From 3b362294b95241511adfe221c968fa8a92515753 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 8 Sep 2020 17:34:11 -0700 Subject: [PATCH 001/112] move quickfort logic from Planner to buildingplan where all the other gui-related logic is. --- plugins/buildingplan-planner.cpp | 27 +++------------------ plugins/buildingplan-planner.h | 10 +------- plugins/buildingplan.cpp | 41 ++++++++++++++++---------------- 3 files changed, 25 insertions(+), 53 deletions(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 31caf995e..6923570c2 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -291,11 +291,9 @@ void PlannedBuilding::remove() * Planner */ -Planner::Planner() : in_dummmy_screen(false), quickfort_mode(false) { } +Planner::Planner() { } -void enable_quickfort_fn(pair& pair) { pair.second = true; } - -bool Planner::isPlanableBuilding(const df::building_type type) const +bool Planner::isPlannableBuilding(const df::building_type type) const { return item_for_building_type.find(type) != item_for_building_type.end(); } @@ -321,8 +319,6 @@ void Planner::initialize() default_item_filters[df::building_type::btype] = ItemFilter(); \ available_item_vectors[df::item_type::itype] = std::vector(); \ is_relevant_item_type[df::item_type::itype] = true; \ - if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \ - planmode_enabled[df::building_type::btype] = false FOR_ENUM_ITEMS(item_type, it) is_relevant_item_type[it] = false; @@ -474,22 +470,6 @@ void Planner::adjustMaxQuality(df::building_type type, int amount) (*min_quality) = *max_quality; } -void Planner::enableQuickfortMode() -{ - saved_planmodes = planmode_enabled; - for_each_(planmode_enabled, enable_quickfort_fn); - - quickfort_mode = true; -} - -void Planner::disableQuickfortMode() -{ - planmode_enabled = saved_planmodes; - quickfort_mode = false; -} - -bool Planner::inQuickFortMode() { return quickfort_mode; } - void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) { *quality = static_cast(*quality); @@ -517,7 +497,7 @@ void Planner::gather_available_items() F(in_building); F(construction); F(artifact); #undef F - std::vector &items = df::global::world->items.other[df::items_other_id::IN_PLAY]; + std::vector &items = df::global::world->items.other[df::items_other_id::IN_PLAY]; for (size_t i = 0; i < items.size(); i++) { @@ -548,5 +528,4 @@ void Planner::gather_available_items() } } -std::map planmode_enabled, saved_planmodes; Planner planner; diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index b073e96b8..67eeb9bff 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -61,11 +61,9 @@ private: class Planner { public: - bool in_dummmy_screen; - Planner(); - bool isPlanableBuilding(const df::building_type type) const; + bool isPlannableBuilding(const df::building_type type) const; void reset(DFHack::color_ostream &out); @@ -86,16 +84,11 @@ public: void adjustMinQuality(df::building_type type, int amount); void adjustMaxQuality(df::building_type type, int amount); - void enableQuickfortMode(); - void disableQuickfortMode(); - bool inQuickFortMode(); - private: std::map item_for_building_type; std::map default_item_filters; std::map> available_item_vectors; std::map is_relevant_item_type; //Needed for fast check when looping over all items - bool quickfort_mode; std::vector planned_buildings; @@ -104,5 +97,4 @@ private: void gather_available_items(); }; -extern std::map planmode_enabled, saved_planmodes; extern Planner planner; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ae8ce8c8d..2ba8f982e 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -28,6 +28,9 @@ using namespace DFHack; using namespace df::enums; bool show_help = false; +bool quickfort_mode = false; +bool in_dummy_screen = false; +std::map planmode_enabled; class ViewscreenChooseMaterial : public dfhack_viewscreen { @@ -316,7 +319,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest return ui->main.mode == ui_sidebar_mode::Build && df::global::ui_build_selector && df::global::ui_build_selector->stage < 2 && - planner.isPlanableBuilding(ui_build_selector->building_type); + planner.isPlannableBuilding(ui_build_selector->building_type); } std::vector getNoblePositionOfSelectedBuildingOwner() @@ -357,11 +360,16 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest auto type = ui_build_selector->building_type; if (input->count(interface_key::CUSTOM_SHIFT_P)) { + if (planmode_enabled.find(type) == planmode_enabled.end()) + { + planmode_enabled[type] = false; + } + planmode_enabled[type] = !planmode_enabled[type]; if (!planmode_enabled[type]) { Gui::refreshSidebar(); - planner.in_dummmy_screen = false; + in_dummy_screen = false; } return true; } @@ -375,12 +383,12 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest if (is_planmode_enabled(type)) { - if (planner.inQuickFortMode() && planner.in_dummmy_screen) + if (quickfort_mode && in_dummy_screen) { if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) || input->count(interface_key::LEAVESCREEN)) { - planner.in_dummmy_screen = false; + in_dummy_screen = false; send_key(interface_key::LEAVESCREEN); } @@ -392,9 +400,9 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) { Gui::refreshSidebar(); - if (planner.inQuickFortMode()) + if (quickfort_mode) { - planner.in_dummmy_screen = true; + in_dummy_screen = true; } } @@ -402,14 +410,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else if (input->count(interface_key::CUSTOM_SHIFT_F)) { - if (!planner.inQuickFortMode()) - { - planner.enableQuickfortMode(); - } - else - { - planner.disableQuickfortMode(); - } + quickfort_mode = !quickfort_mode; } else if (input->count(interface_key::CUSTOM_SHIFT_M)) { @@ -509,7 +510,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest auto type = ui_build_selector->building_type; if (plannable) { - if (planner.inQuickFortMode() && planner.in_dummmy_screen) + if (quickfort_mode && in_dummy_screen) { Screen::Pen pen(' ',COLOR_BLACK); int y = dims.y1 + 1; @@ -533,7 +534,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest if (is_planmode_enabled(type)) { - OutputToggleString(x, y, "Quickfort Mode", "F", planner.inQuickFortMode(), true, left_margin); + OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); auto filter = planner.getDefaultItemFilterForType(type); @@ -552,13 +553,13 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else { - planner.in_dummmy_screen = false; + in_dummy_screen = false; } } } else if (isInPlannedBuildingQueryMode()) { - planner.in_dummmy_screen = false; + in_dummy_screen = false; // Hide suspend toggle option int y = 20; @@ -597,7 +598,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else { - planner.in_dummmy_screen = false; + in_dummy_screen = false; show_help = false; } } @@ -690,7 +691,7 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) // Lua API section static bool isPlannableBuilding(df::building_type type) { - return planner.isPlanableBuilding(type); + return planner.isPlannableBuilding(type); } static void addPlannedBuilding(df::building *bld) { From 1c2761f90384e4eeedd4364cc4a012be2512fce1 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 23 Sep 2020 20:00:04 -0700 Subject: [PATCH 002/112] enable all in quickfort mode and simplify logic since new bool map values are already initialized to false --- plugins/buildingplan.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 2ba8f982e..93d5ff4e4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -287,12 +287,7 @@ void ViewscreenChooseMaterial::render() //START Viewscreen Hook static bool is_planmode_enabled(df::building_type type) { - if (planmode_enabled.find(type) == planmode_enabled.end()) - { - return false; - } - - return planmode_enabled[type]; + return planmode_enabled[type] || quickfort_mode; } struct buildingplan_hook : public df::viewscreen_dwarfmodest @@ -360,13 +355,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest auto type = ui_build_selector->building_type; if (input->count(interface_key::CUSTOM_SHIFT_P)) { - if (planmode_enabled.find(type) == planmode_enabled.end()) - { - planmode_enabled[type] = false; - } - planmode_enabled[type] = !planmode_enabled[type]; - if (!planmode_enabled[type]) + if (!is_planmode_enabled(type)) { Gui::refreshSidebar(); in_dummy_screen = false; From 4d7f4d80ad478d2735a815aec5a5edb2dea665d0 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 4 Oct 2020 20:05:08 -0700 Subject: [PATCH 003/112] prep buildingplan for core algorithm changes player-visible changes - removed text that showed up if you used the wrong hotkeys. no other dfhack screen does this, and it seems unneeded. can add back if others think otherwise, though internal changes - changed signature of lua-exported isPlannableBuilding to take subtype and custom type in addition to building type. this is only used by quickfort, and it already sends all three params in preparation for this change - added lua-exported scheduleCycle(), which is like doCycle(), but only takes effect on the next non-paused frame. this lets quickfort run only one buildingplan cycle regardless of how many #build blueprints were run - declared a few dfhack library methods and params const so buildingplan could call them from const methods - converted buildingplan internal debug logging fn to have a printf api - reshaped buildingplan-planner API and refactored implementation in preparation for upcoming core algorithm changes for supporing all building types (no externally-visible functionality changes) - changed df::building_type params to type, subtype, custom tuple keys - introduced capability to return multiple filters per building type (though the current buildings all only have one filter per) - split monolith hook functions in buildingplan.cpp into one per scope. this significantly cleans up the code and preps the hooks to handle iterating through multiple item filters. - got rid of send_key function and replaced with better reporting of whether keys have been handled --- docs/Lua API.rst | 3 +- library/include/modules/Materials.h | 4 +- library/modules/Materials.cpp | 4 +- plugins/buildingplan-lib.cpp | 10 +- plugins/buildingplan-lib.h | 2 +- plugins/buildingplan-planner.cpp | 534 ++++++++++++++++----------- plugins/buildingplan-planner.h | 130 ++++--- plugins/buildingplan.cpp | 538 +++++++++++++++------------- plugins/fortplan.cpp | 4 +- plugins/lua/buildingplan.lua | 3 +- plugins/uicommon.h | 2 +- 11 files changed, 704 insertions(+), 530 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 8707a0d99..19474e4d8 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3691,9 +3691,10 @@ buildingplan Native functions: -* ``bool isPlannableBuilding(df::building_type type)`` returns whether the building type is handled by buildingplan +* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan * ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list * ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. +* ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. burrows ======= diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index b326719c6..a3b4204e5 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -136,8 +136,8 @@ namespace DFHack return findProduct(info.material, name); } - std::string getToken(); - std::string toString(uint16_t temp = 10015, bool named = true); + std::string getToken() const; + std::string toString(uint16_t temp = 10015, bool named = true) const; bool isAnyCloth(); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index c341ea99e..909fa56f9 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -305,7 +305,7 @@ bool MaterialInfo::findProduct(df::material *material, const std::string &name) return decode(-1); } -std::string MaterialInfo::getToken() +std::string MaterialInfo::getToken() const { if (isNone()) return "NONE"; @@ -333,7 +333,7 @@ std::string MaterialInfo::getToken() } } -std::string MaterialInfo::toString(uint16_t temp, bool named) +std::string MaterialInfo::toString(uint16_t temp, bool named) const { if (isNone()) return "any"; diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index 215dbb900..e05d9b9dc 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -1,16 +1,22 @@ #include "buildingplan-lib.h" +#include #include "Core.h" using namespace DFHack; bool show_debugging = false; -void debug(const std::string &msg) +void debug(const char *fmt, ...) { if (!show_debugging) return; color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG: " << msg << endl; + out.print("DEBUG(buildingplan): "); + va_list args; + va_start(args, fmt); + out.vprint(fmt, args); + va_end(args); + out.print("\n"); } diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 61a00e796..e906ef1a7 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -3,6 +3,6 @@ #include "buildingplan-planner.h" #include "buildingplan-rooms.h" -void debug(const std::string &msg); +void debug(const char *fmt, ...) Wformat(printf,1,2); extern bool show_debugging; diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 6923570c2..c367ecc33 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -1,185 +1,265 @@ +#include "df/building_design.h" +#include "df/building_doorst.h" #include "df/general_ref_building_holderst.h" #include "df/job_item.h" -#include "df/building_doorst.h" -#include "df/building_design.h" +#include "df/ui_build_selector.h" -#include "modules/Job.h" #include "modules/Buildings.h" #include "modules/Gui.h" +#include "modules/Job.h" + +#include "uicommon.h" #include "buildingplan-planner.h" #include "buildingplan-lib.h" -#include "uicommon.h" -/* -* ItemFilter -*/ +static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints"; -ItemFilter::ItemFilter() : - min_quality(df::item_quality::Ordinary), - max_quality(df::item_quality::Artifact), - decorated_only(false), - valid(true) -{ - clear(); // mat_mask is not cleared by default (see issue #1047) -} +/* + * ItemFilter + */ -bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) +ItemFilter::ItemFilter() { - return (mat_mask.whole) ? mat.matches(mat_mask) : true; + clear(); } -bool ItemFilter::matches(const df::dfhack_material_category mask) const +void ItemFilter::clear() { - return mask.whole & mat_mask.whole; + min_quality = df::item_quality::Ordinary; + max_quality = df::item_quality::Masterful; + decorated_only = false; + clearMaterialMask(); + materials.clear(); } -bool ItemFilter::matches(DFHack::MaterialInfo &material) const +bool ItemFilter::deserialize(PersistentDataItem &config) { - for (auto it = materials.begin(); it != materials.end(); ++it) - if (material.matches(*it)) - return true; - return false; -} + clear(); -bool ItemFilter::matches(df::item *item) -{ - if (item->getQuality() < min_quality || item->getQuality() > max_quality) + std::vector tokens; + split_string(&tokens, config.val(), "/"); + if (tokens.size() != 2) + { + debug("invalid ItemFilter serialization: '%s'", config.val().c_str()); return false; + } - if (decorated_only && !item->hasImprovements()) + if (!deserializeMaterialMask(tokens[0]) || !deserializeMaterials(tokens[1])) return false; - auto imattype = item->getActualMaterial(); - auto imatindex = item->getActualMaterialIndex(); - auto item_mat = DFHack::MaterialInfo(imattype, imatindex); - - return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); + setMinQuality(config.ival(2) - 1); + setMaxQuality(config.ival(4) - 1); + decorated_only = config.ival(3) - 1; + return true; } -std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } +bool ItemFilter::deserializeMaterialMask(std::string ser) +{ + if (ser.empty()) + return true; -std::vector ItemFilter::getMaterialFilterAsVector() + if (!parseJobMaterialCategory(&mat_mask, ser)) + { + debug("invalid job material category serialization: '%s'", ser.c_str()); + return false; + } + return true; +} + +bool ItemFilter::deserializeMaterials(std::string ser) { - std::vector descriptions; + if (ser.empty()) + return true; - transform_(materials, descriptions, material_to_string_fn); + std::vector mat_names; + split_string(&mat_names, ser, ","); + for (auto m = mat_names.begin(); m != mat_names.end(); m++) + { + DFHack::MaterialInfo material; + if (!material.find(*m) || !material.isValid()) + { + debug("invalid material name serialization: '%s'", ser.c_str()); + return false; + } + materials.push_back(material); + } + return true; +} - if (descriptions.size() == 0) - bitfield_to_string(&descriptions, mat_mask); +void ItemFilter::serialize(PersistentDataItem &config) const +{ + std::ostringstream ser; + ser << bitfield_to_string(mat_mask, ",") << "/"; + if (!materials.empty()) + { + ser << materials[0].getToken(); + for (size_t i = 1; i < materials.size(); ++i) + ser << "," << materials[i].getToken(); + } + config.val() = ser.str(); + config.ival(2) = min_quality + 1; + config.ival(4) = max_quality + 1; + config.ival(3) = static_cast(decorated_only) + 1; +} - if (descriptions.size() == 0) - descriptions.push_back("any"); +void ItemFilter::clearMaterialMask() +{ + mat_mask.whole = 0; +} - return descriptions; +void ItemFilter::addMaterialMask(uint32_t mask) +{ + mat_mask.whole |= mask; } -std::string ItemFilter::getMaterialFilterAsSerial() +void ItemFilter::setMaterials(std::vector materials) { - std::string str; + this->materials = materials; +} - str.append(bitfield_to_string(mat_mask, ",")); - str.append("/"); - if (materials.size() > 0) +static void clampItemQuality(df::item_quality *quality) +{ + if (*quality > item_quality::Artifact) { - for (size_t i = 0; i < materials.size(); i++) - str.append(materials[i].getToken() + ","); - - if (str[str.size()-1] == ',') - str.resize(str.size () - 1); + debug("clamping quality to Artifact"); + *quality = item_quality::Artifact; } + if (*quality < item_quality::Ordinary) + { + debug("clamping quality to Ordinary"); + *quality = item_quality::Ordinary; + } +} - return str; +void ItemFilter::setMinQuality(int quality) +{ + min_quality = static_cast(quality); + clampItemQuality(&min_quality); + if (max_quality < min_quality) + max_quality = min_quality; } -bool ItemFilter::parseSerializedMaterialTokens(std::string str) +void ItemFilter::setMaxQuality(int quality) { - valid = false; - std::vector tokens; - split_string(&tokens, str, "/"); + max_quality = static_cast(quality); + clampItemQuality(&max_quality); + if (max_quality < min_quality) + min_quality = max_quality; +} - if (tokens.size() > 0 && !tokens[0].empty()) - { - if (!parseJobMaterialCategory(&mat_mask, tokens[0])) - return false; - } +void ItemFilter::incMinQuality() { setMinQuality(min_quality + 1); } +void ItemFilter::decMinQuality() { setMinQuality(min_quality - 1); } +void ItemFilter::incMaxQuality() { setMaxQuality(max_quality + 1); } +void ItemFilter::decMaxQuality() { setMaxQuality(max_quality - 1); } - if (tokens.size() > 1 && !tokens[1].empty()) - { - std::vector mat_names; - split_string(&mat_names, tokens[1], ","); - for (auto m = mat_names.begin(); m != mat_names.end(); m++) - { - DFHack::MaterialInfo material; - if (!material.find(*m) || !material.isValid()) - return false; +void ItemFilter::toggleDecoratedOnly() { decorated_only = !decorated_only; } - materials.push_back(material); - } - } +static std::string material_to_string_fn(const MaterialInfo &m) { return m.toString(); } - valid = true; - return true; +uint32_t ItemFilter::getMaterialMask() const { return mat_mask.whole; } + +std::vector ItemFilter::getMaterials() const +{ + std::vector descriptions; + transform_(materials, descriptions, material_to_string_fn); + + if (descriptions.size() == 0) + bitfield_to_string(&descriptions, mat_mask); + + if (descriptions.size() == 0) + descriptions.push_back("any"); + + return descriptions; } -std::string ItemFilter::getMinQuality() +std::string ItemFilter::getMinQuality() const { return ENUM_KEY_STR(item_quality, min_quality); } -std::string ItemFilter::getMaxQuality() +std::string ItemFilter::getMaxQuality() const { return ENUM_KEY_STR(item_quality, max_quality); } -bool ItemFilter::isValid() +bool ItemFilter::getDecoratedOnly() const { - return valid; + return decorated_only; } -void ItemFilter::clear() +bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) const { - mat_mask.whole = 0; - materials.clear(); + return mat_mask.whole ? mat.matches(mat_mask) : true; } -/* -* PlannedBuilding -*/ +bool ItemFilter::matches(df::dfhack_material_category mask) const +{ + return mask.whole & mat_mask.whole; +} -PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) +bool ItemFilter::matches(DFHack::MaterialInfo &material) const { - this->building = building; - this->filter = *filter; - pos = df::coord(building->centerx, building->centery, building->z); - config = DFHack::World::AddPersistentData("buildingplan/constraints"); - config.val() = filter->getMaterialFilterAsSerial(); - config.ival(1) = building->id; - config.ival(2) = filter->min_quality + 1; - config.ival(3) = static_cast(filter->decorated_only) + 1; - config.ival(4) = filter->max_quality + 1; + for (auto it = materials.begin(); it != materials.end(); ++it) + if (material.matches(*it)) + return true; + return false; } -PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out) +bool ItemFilter::matches(df::item *item) const { - this->config = config; + if (item->getQuality() < min_quality || item->getQuality() > max_quality) + return false; - if (!filter.parseSerializedMaterialTokens(config.val())) - { - out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); - return; - } + if (decorated_only && !item->hasImprovements()) + return false; - building = df::building::find(config.ival(1)); - if (!building) - return; + auto imattype = item->getActualMaterial(); + auto imatindex = item->getActualMaterialIndex(); + auto item_mat = DFHack::MaterialInfo(imattype, imatindex); - pos = df::coord(building->centerx, building->centery, building->z); - filter.min_quality = static_cast(config.ival(2) - 1); - filter.max_quality = static_cast(config.ival(4) - 1); - filter.decorated_only = config.ival(3) - 1; + return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); } + +/* + * PlannedBuilding + */ + +static std::vector deserializeFilters(PersistentDataItem &config) +{ + // simplified implementation while we can assume there is only one filter + std::vector ret; + ItemFilter itemFilter; + itemFilter.deserialize(config); + ret.push_back(itemFilter); + return ret; +} + +static int getNumFilters(BuildingTypeKey key) +{ + // TODO: get num filters in Lua when we handle all building types + return 1; +} + +PlannedBuilding::PlannedBuilding(df::building *building, const std::vector &filters) + : building(building), + building_id(building->id), + filters(filters) +{ + config = DFHack::World::AddPersistentData(planned_building_persistence_key_v1); + config.ival(1) = building_id; + // assume all filter vectors are length 1 for now + filters[0].serialize(config); +} + +PlannedBuilding::PlannedBuilding(PersistentDataItem &config) + : config(config), + building_id(config.ival(1)), + building(df::building::find(config.ival(1))), + filters(deserializeFilters(config)) +{ } + bool PlannedBuilding::assignClosestItem(std::vector *items_vector) { decltype(items_vector->begin()) closest_item; @@ -187,7 +267,7 @@ bool PlannedBuilding::assignClosestItem(std::vector *items_vector) for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) { auto item = *item_iter; - if (!filter.matches(item)) + if (!filters[0].matches(item)) continue; auto pos = item->pos; @@ -255,68 +335,70 @@ bool PlannedBuilding::assignItem(df::item *item) return true; } -bool PlannedBuilding::isValid() +// Ensure the building still exists and is in a valid state. It can disappear +// for lots of reasons, such as running the game with the buildingplan plugin +// disabled, manually removing the building, modifying it via the API, etc. +bool PlannedBuilding::isValid() const { - bool valid = filter.isValid() && - building && Buildings::findAtTile(pos) == building && - building->getBuildStage() == 0; - - if (!valid) - remove(); - - return valid; + return building && df::building::find(building_id) + && building->getBuildStage() == 0; } -df::building_type PlannedBuilding::getType() +void PlannedBuilding::remove() { - return building->getType(); + DFHack::World::DeletePersistentData(config); + building = NULL; } -bool PlannedBuilding::isCurrentlySelectedBuilding() +df::building * PlannedBuilding::getBuilding() { - return isValid() && (building == df::global::world->selected_building); + return building; } -ItemFilter *PlannedBuilding::getFilter() +const std::vector & PlannedBuilding::getFilters() const { - return &filter; + return filters; } -void PlannedBuilding::remove() -{ - DFHack::World::DeletePersistentData(config); -} /* -* Planner -*/ + * BuildingTypeKey + */ -Planner::Planner() { } +BuildingTypeKey toBuildingTypeKey( + df::building_type btype, int16_t subtype, int32_t custom) +{ + return std::make_tuple(btype, subtype, custom); +} -bool Planner::isPlannableBuilding(const df::building_type type) const +BuildingTypeKey toBuildingTypeKey(df::building *bld) { - return item_for_building_type.find(type) != item_for_building_type.end(); + return std::make_tuple( + bld->getType(), bld->getSubtype(), bld->getCustomType()); } -void Planner::reset(color_ostream &out) +BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs) { - planned_buildings.clear(); - std::vector items; - DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); + return std::make_tuple( + uibs->building_type, uibs->building_subtype, uibs->custom_type); +} - for (auto i = items.begin(); i != items.end(); i++) - { - PlannedBuilding pb(*i, out); - if (pb.isValid()) - planned_buildings.push_back(pb); - } +std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const +{ + return std::hash()(std::get<0>(key)) + ^ std::hash()(std::get<1>(key)) + ^ std::hash()(std::get<2>(key)); } + +/* + * Planner + */ + void Planner::initialize() { #define add_building_type(btype, itype) \ item_for_building_type[df::building_type::btype] = df::item_type::itype; \ - default_item_filters[df::building_type::btype] = ItemFilter(); \ available_item_vectors[df::item_type::itype] = std::vector(); \ is_relevant_item_type[df::item_type::itype] = true; \ @@ -354,53 +436,82 @@ void Planner::initialize() #undef add_building_type } -void Planner::addPlannedBuilding(df::building *bld) +void Planner::reset() { - for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++) + debug("resetting Planner state"); + item_filters.clear(); + planned_buildings.clear(); + + std::vector items; + DFHack::World::GetPersistentData(&items, planned_building_persistence_key_v1); + debug("found data for %zu planned buildings", items.size()); + + for (auto i = items.begin(); i != items.end(); i++) { - (*iter)->flags.bits.suspend = true; - } + PlannedBuilding pb(*i); + if (!pb.isValid()) + { + pb.remove(); + continue; + } - PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); - planned_buildings.push_back(pb); + planned_buildings.push_back(pb); + } } -void Planner::doCycle() +void Planner::addPlannedBuilding(df::building *bld) { - debug("Running Cycle"); - if (planned_buildings.size() == 0) + auto item_filters = getItemFilters(toBuildingTypeKey(bld)).get(); + // not a supported type + if (item_filters.empty()) + { + debug("failed to add building: unsupported type"); + return; + } + + // protect against multiple registrations + if (getPlannedBuilding(bld)) + { + debug("building already registered"); return; + } - debug("Planned count: " + int_to_string(planned_buildings.size())); + PlannedBuilding pb(bld, item_filters); + if (pb.isValid()) + { + for (auto job : bld->jobs) + job->flags.bits.suspend = true; - gather_available_items(); - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + planned_buildings.push_back(pb); + } + else { - if (building_iter->isValid()) - { - if (show_debugging) - debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); + pb.remove(); + } +} - auto required_item_type = item_for_building_type[building_iter->getType()]; - auto items_vector = &available_item_vectors[required_item_type]; - if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) - { - debug("Unable to allocate an item"); - ++building_iter; - continue; - } - } - debug("Removing building plan"); - building_iter = planned_buildings.erase(building_iter); +PlannedBuilding * Planner::getPlannedBuilding(df::building *bld) +{ + for (auto & pb : planned_buildings) + { + if (pb.getBuilding() == bld) + return &pb; } + return NULL; +} + +bool Planner::isPlannableBuilding(BuildingTypeKey key) +{ + return item_for_building_type.count(std::get<0>(key)) > 0; } -bool Planner::allocatePlannedBuilding(df::building_type type) +bool Planner::allocatePlannedBuilding(BuildingTypeKey key) { coord32_t cursor; if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) return false; + auto type = std::get<0>(key); auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); if (!newinst) return false; @@ -430,53 +541,48 @@ bool Planner::allocatePlannedBuilding(df::building_type type) return true; } -PlannedBuilding *Planner::getSelectedPlannedBuilding() +Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) { - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) - { - if (building_iter->isCurrentlySelectedBuilding()) - { - return &(*building_iter); - } - } + static std::vector empty_vector; + static const ItemFiltersWrapper empty_ret(empty_vector); - return nullptr; + int nfilters = getNumFilters(key); + if (nfilters < 1) + return empty_ret; + std::vector ret; + ret.push_back(item_filters[std::get<0>(key)]); + return ItemFiltersWrapper(ret); } -void Planner::removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); } - -ItemFilter *Planner::getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; } - -void Planner::adjustMinQuality(df::building_type type, int amount) +void Planner::doCycle() { - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - *min_quality = static_cast(*min_quality + amount); - - boundsCheckItemQuality(min_quality); - auto max_quality = &getDefaultItemFilterForType(type)->max_quality; - if (*min_quality > *max_quality) - (*max_quality) = *min_quality; + debug("Running Cycle"); + if (planned_buildings.size() == 0) + return; -} + debug("Planned count: %zu", planned_buildings.size()); -void Planner::adjustMaxQuality(df::building_type type, int amount) -{ - auto max_quality = &getDefaultItemFilterForType(type)->max_quality; - *max_quality = static_cast(*max_quality + amount); - - boundsCheckItemQuality(max_quality); - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - if (*max_quality < *min_quality) - (*min_quality) = *max_quality; -} + gather_available_items(); + for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + { + if (building_iter->isValid()) + { + auto type = building_iter->getBuilding()->getType(); + debug("Trying to allocate %s", enum_item_key_str(type)); -void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) -{ - *quality = static_cast(*quality); - if (*quality > item_quality::Artifact) - (*quality) = item_quality::Artifact; - if (*quality < item_quality::Ordinary) - (*quality) = item_quality::Ordinary; + auto required_item_type = item_for_building_type[type]; + auto items_vector = &available_item_vectors[required_item_type]; + if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) + { + debug("Unable to allocate an item"); + ++building_iter; + continue; + } + } + debug("Removing building plan"); + building_iter->remove(); + building_iter = planned_buildings.erase(building_iter); + } } void Planner::gather_available_items() diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index 67eeb9bff..a1f53dcd2 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -1,99 +1,129 @@ #pragma once -#include "df/item_quality.h" +#include + +#include "df/building.h" #include "df/dfhack_material_category.h" +#include "df/item_quality.h" +#include "df/job_item.h" + #include "modules/Materials.h" #include "modules/Persistence.h" -struct ItemFilter +class ItemFilter { +public: + ItemFilter(); + + void clear(); + bool deserialize(DFHack::PersistentDataItem &config); + void serialize(DFHack::PersistentDataItem &config) const; + + void addMaterialMask(uint32_t mask); + void clearMaterialMask(); + void setMaterials(std::vector materials); + + void incMinQuality(); + void decMinQuality(); + void incMaxQuality(); + void decMaxQuality(); + void toggleDecoratedOnly(); + + uint32_t getMaterialMask() const; + std::vector getMaterials() const; + std::string getMinQuality() const; + std::string getMaxQuality() const; + bool getDecoratedOnly() const; + + bool matches(df::dfhack_material_category mask) const; + bool matches(DFHack::MaterialInfo &material) const; + bool matches(df::item *item) const; + +private: df::dfhack_material_category mat_mask; std::vector materials; df::item_quality min_quality; df::item_quality max_quality; bool decorated_only; - ItemFilter(); - - bool matchesMask(DFHack::MaterialInfo &mat); - bool matches(const df::dfhack_material_category mask) const; - bool matches(DFHack::MaterialInfo &material) const; - bool matches(df::item *item); - - std::vector getMaterialFilterAsVector(); - std::string getMaterialFilterAsSerial(); - bool parseSerializedMaterialTokens(std::string str); - - std::string getMinQuality(); - std::string getMaxQuality(); - - bool isValid(); - void clear(); - -private: - bool valid; + bool deserializeMaterialMask(std::string ser); + bool deserializeMaterials(std::string ser); + void setMinQuality(int quality); + void setMaxQuality(int quality); + bool matchesMask(DFHack::MaterialInfo &mat) const; }; class PlannedBuilding { public: - PlannedBuilding(df::building *building, ItemFilter *filter); - PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); + PlannedBuilding(df::building *building, const std::vector &filters); + PlannedBuilding(DFHack::PersistentDataItem &config); bool assignClosestItem(std::vector *items_vector); bool assignItem(df::item *item); - bool isValid(); + bool isValid() const; void remove(); - df::building_type getType(); - bool isCurrentlySelectedBuilding(); - - ItemFilter *getFilter(); + df::building * getBuilding(); + const std::vector & getFilters() const; private: - df::building *building; DFHack::PersistentDataItem config; - df::coord pos; - ItemFilter filter; + df::building *building; + df::building::key_field_type building_id; + std::vector filters; +}; + +// building type, subtype, custom +typedef std::tuple BuildingTypeKey; + +BuildingTypeKey toBuildingTypeKey( + df::building_type btype, int16_t subtype, int32_t custom); +BuildingTypeKey toBuildingTypeKey(df::building *bld); +BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs); + +struct BuildingTypeKeyHash +{ + std::size_t operator() (const BuildingTypeKey & key) const; }; class Planner { public: - Planner(); - - bool isPlannableBuilding(const df::building_type type) const; - - void reset(DFHack::color_ostream &out); + class ItemFiltersWrapper + { + public: + ItemFiltersWrapper(std::vector & item_filters) + : item_filters(item_filters) { } + std::vector::reverse_iterator rbegin() const { return item_filters.rbegin(); } + std::vector::reverse_iterator rend() const { return item_filters.rend(); } + const std::vector & get() const { return item_filters; } + private: + std::vector &item_filters; + }; void initialize(); + void reset(); + bool allocatePlannedBuilding(BuildingTypeKey key); void addPlannedBuilding(df::building *bld); + PlannedBuilding *getPlannedBuilding(df::building *bld); - void doCycle(); - - bool allocatePlannedBuilding(df::building_type type); - - PlannedBuilding *getSelectedPlannedBuilding(); + bool isPlannableBuilding(BuildingTypeKey key); - void removeSelectedPlannedBuilding(); + // returns an empty vector if the type is not supported + ItemFiltersWrapper getItemFilters(BuildingTypeKey key); - ItemFilter *getDefaultItemFilterForType(df::building_type type); - - void adjustMinQuality(df::building_type type, int amount); - void adjustMaxQuality(df::building_type type, int amount); + void doCycle(); private: std::map item_for_building_type; - std::map default_item_filters; + std::map item_filters; std::map> available_item_vectors; std::map is_relevant_item_type; //Needed for fast check when looping over all items - std::vector planned_buildings; - void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality); - void gather_available_items(); }; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 93d5ff4e4..6cb45c879 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,5 +1,5 @@ -#include "buildingplan-lib.h" - +#include + #include "df/entity_position.h" #include "df/interface_key.h" #include "df/ui_build_selector.h" @@ -14,6 +14,7 @@ #include "uicommon.h" #include "listcolumn.h" +#include "buildingplan-lib.h" DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.15 @@ -27,15 +28,14 @@ REQUIRE_GLOBAL(world); using namespace DFHack; using namespace df::enums; -bool show_help = false; bool quickfort_mode = false; bool in_dummy_screen = false; -std::map planmode_enabled; +std::unordered_map planmode_enabled; class ViewscreenChooseMaterial : public dfhack_viewscreen { public: - ViewscreenChooseMaterial(ItemFilter *filter); + ViewscreenChooseMaterial(ItemFilter &filter); void feed(set *input); @@ -47,14 +47,12 @@ private: ListColumn masks_column; ListColumn materials_column; int selected_column; - ItemFilter *filter; - - df::building_type btype; + ItemFilter &filter; void addMaskEntry(df::dfhack_material_category &mask, const std::string &text) { auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); - if (filter->matches(mask)) + if (filter.matches(mask)) entry.selected = true; masks_column.add(entry); @@ -131,7 +129,7 @@ private: material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); auto name = material.toString(); ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) + if (filter.matches(material)) entry.selected = true; materials_column.add(entry); @@ -147,7 +145,7 @@ private: if (!selected_category.whole || material.matches(selected_category)) { ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) + if (filter.matches(material)) entry.selected = true; materials_column.add(entry); @@ -167,9 +165,10 @@ private: } }; -DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } +const DFHack::MaterialInfo &material_info_identity_fn(const DFHack::MaterialInfo &m) { return m; } -ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) +ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter &filter) + : filter(filter) { selected_column = 0; masks_column.setTitle("Type"); @@ -179,7 +178,6 @@ ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) materials_column.left_margin = MAX_MASK + 3; materials_column.setTitle("Material"); materials_column.multiselect = true; - this->filter = filter; masks_column.changeHighlight(0); @@ -217,7 +215,7 @@ void ViewscreenChooseMaterial::feed(set *input) } if (input->count(interface_key::CUSTOM_SHIFT_C)) { - filter->clear(); + filter.clear(); masks_column.clearSelection(); materials_column.clearSelection(); populateMaterials(); @@ -225,17 +223,18 @@ void ViewscreenChooseMaterial::feed(set *input) else if (input->count(interface_key::SEC_SELECT)) { // Convert list selections to material filters - filter->mat_mask.whole = 0; - filter->materials.clear(); + filter.clearMaterialMask(); // Category masks auto masks = masks_column.getSelectedElems(); for (auto it = masks.begin(); it != masks.end(); ++it) - filter->mat_mask.whole |= it->whole; + filter.addMaterialMask(it->whole); // Specific materials auto materials = materials_column.getSelectedElems(); - transform_(materials, filter->materials, material_info_identity_fn); + std::vector materialInfos; + transform_(materials, materialInfos, material_info_identity_fn); + filter.setMaterials(materialInfos); Screen::dismiss(this); } @@ -285,179 +284,157 @@ void ViewscreenChooseMaterial::render() } //START Viewscreen Hook -static bool is_planmode_enabled(df::building_type type) +static bool is_planmode_enabled(BuildingTypeKey key) { - return planmode_enabled[type] || quickfort_mode; + return planmode_enabled[key] || quickfort_mode; } -struct buildingplan_hook : public df::viewscreen_dwarfmodest +struct buildingplan_query_hook : public df::viewscreen_dwarfmodest { - //START UI Methods typedef df::viewscreen_dwarfmodest interpose_base; - void send_key(const df::interface_key &key) - { - set< df::interface_key > keys; - keys.insert(key); - this->feed(&keys); - } - bool isInPlannedBuildingQueryMode() { return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding || ui->main.mode == df::ui_sidebar_mode::BuildingItems) && - planner.getSelectedPlannedBuilding(); + planner.getPlannedBuilding(world->selected_building); } - bool isInPlannedBuildingPlacementMode() + bool handleInput(set *input) { - return ui->main.mode == ui_sidebar_mode::Build && - df::global::ui_build_selector && - df::global::ui_build_selector->stage < 2 && - planner.isPlannableBuilding(ui_build_selector->building_type); - } + if (!isInPlannedBuildingQueryMode()) + return false; - std::vector getNoblePositionOfSelectedBuildingOwner() - { - std::vector np; - if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding || - !world->selected_building || - !world->selected_building->owner) + if (input->count(interface_key::SUSPENDBUILDING)) + return true; // Don't unsuspend planned buildings + if (input->count(interface_key::DESTROYBUILDING)) { - return np; + // remove persistent data + planner.getPlannedBuilding(world->selected_building)->remove(); + // still allow the building to be removed + return false; } - switch (world->selected_building->getType()) - { - case building_type::Bed: - case building_type::Chair: - case building_type::Table: - break; - default: - return np; - } + return true; + } - return getUniqueNoblePositions(world->selected_building->owner); + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); } - bool isInNobleRoomQueryMode() + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { - if (getNoblePositionOfSelectedBuildingOwner().size() > 0) - return canReserveRoom(world->selected_building); - else - return false; + INTERPOSE_NEXT(render)(); + + if (!isInPlannedBuildingQueryMode()) + return; + + // Hide suspend toggle option + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 20; + Screen::Pen pen(' ', COLOR_BLACK); + Screen::fillRect(pen, x, y, dims.menu_x2, y); + + // all current buildings only have one filter + auto & filter = planner.getPlannedBuilding(world->selected_building)->getFilters()[0]; + y = 24; + OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin); + ++y; + OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin); + OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin); + + if (filter.getDecoratedOnly()) + OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); + + OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); + auto filters = filter.getMaterials(); + for (auto it = filters.begin(); it != filters.end(); ++it) + OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); + } +}; + +struct buildingplan_place_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + bool isInPlannedBuildingPlacementMode() + { + return ui->main.mode == ui_sidebar_mode::Build && + df::global::ui_build_selector && + df::global::ui_build_selector->stage < 2 && + planner.isPlannableBuilding(toBuildingTypeKey(ui_build_selector)); } bool handleInput(set *input) { - if (isInPlannedBuildingPlacementMode()) + if (!isInPlannedBuildingPlacementMode()) + return false; + + if (in_dummy_screen) { - auto type = ui_build_selector->building_type; - if (input->count(interface_key::CUSTOM_SHIFT_P)) - { - planmode_enabled[type] = !planmode_enabled[type]; - if (!is_planmode_enabled(type)) - { - Gui::refreshSidebar(); - in_dummy_screen = false; - } - return true; - } - else if (input->count(interface_key::CUSTOM_P) || - input->count(interface_key::CUSTOM_F) || - input->count(interface_key::CUSTOM_D) || - input->count(interface_key::CUSTOM_N)) + if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) + || input->count(interface_key::LEAVESCREEN)) { - show_help = true; + in_dummy_screen = false; + // pass LEAVESCREEN up to parent view + input->clear(); + input->insert(interface_key::LEAVESCREEN); + return false; } + return true; + } - if (is_planmode_enabled(type)) - { - if (quickfort_mode && in_dummy_screen) - { - if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) - || input->count(interface_key::LEAVESCREEN)) - { - in_dummy_screen = false; - send_key(interface_key::LEAVESCREEN); - } - - return true; - } - - if (input->count(interface_key::SELECT)) - { - if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) - { - Gui::refreshSidebar(); - if (quickfort_mode) - { - in_dummy_screen = true; - } - } - - return true; - } - else if (input->count(interface_key::CUSTOM_SHIFT_F)) - { - quickfort_mode = !quickfort_mode; - } - else if (input->count(interface_key::CUSTOM_SHIFT_M)) - { - Screen::show(dts::make_unique(planner.getDefaultItemFilterForType(type)), plugin_self); - } - else if (input->count(interface_key::CUSTOM_Q)) - { - planner.adjustMinQuality(type, -1); - } - else if (input->count(interface_key::CUSTOM_W)) - { - planner.adjustMinQuality(type, 1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_Q)) - { - planner.adjustMaxQuality(type, -1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_W)) - { - planner.adjustMaxQuality(type, 1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_D)) - { - planner.getDefaultItemFilterForType(type)->decorated_only = - !planner.getDefaultItemFilterForType(type)->decorated_only; - } - } + BuildingTypeKey key = toBuildingTypeKey(ui_build_selector); + if (input->count(interface_key::CUSTOM_SHIFT_P)) + { + planmode_enabled[key] = !planmode_enabled[key]; + if (!is_planmode_enabled(key)) + Gui::refreshSidebar(); + return true; } - else if (isInPlannedBuildingQueryMode()) + if (input->count(interface_key::CUSTOM_SHIFT_F)) { - if (input->count(interface_key::SUSPENDBUILDING)) - { - return true; // Don't unsuspend planned buildings - } - else if (input->count(interface_key::DESTROYBUILDING)) - { - planner.removeSelectedPlannedBuilding(); // Remove persistent data - } - + quickfort_mode = !quickfort_mode; + return true; } - else if (isInNobleRoomQueryMode()) + + if (!is_planmode_enabled(key)) + return false; + + if (input->count(interface_key::SELECT)) { - if (Gui::inRenameBuilding()) - return false; - auto np = getNoblePositionOfSelectedBuildingOwner(); - df::interface_key last_token = get_string_key(input); - if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) + if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(key)) { - size_t selection = last_token - interface_key::STRING_A048; - if (np.size() < selection) - return false; - roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); - return true; + Gui::refreshSidebar(); + if (quickfort_mode) + in_dummy_screen = true; } + return true; } - return false; + // all current buildings only have one filter + auto filter = planner.getItemFilters(key).rbegin(); + if (input->count(interface_key::CUSTOM_SHIFT_M)) + Screen::show(dts::make_unique(*filter), plugin_self); + else if (input->count(interface_key::CUSTOM_Q)) + filter->decMinQuality(); + else if (input->count(interface_key::CUSTOM_W)) + filter->incMinQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_Q)) + filter->decMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_W)) + filter->incMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_D)) + filter->toggleDecoratedOnly(); + else + return false; + return true; } DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) @@ -469,133 +446,170 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest DEFINE_VMETHOD_INTERPOSE(void, render, ()) { bool plannable = isInPlannedBuildingPlacementMode(); - if (plannable && is_planmode_enabled(ui_build_selector->building_type)) + BuildingTypeKey key = toBuildingTypeKey(ui_build_selector); + if (plannable && is_planmode_enabled(key)) { if (ui_build_selector->stage < 1) - { // No materials but turn on cursor ui_build_selector->stage = 1; - } - for (auto iter = ui_build_selector->errors.begin(); iter != ui_build_selector->errors.end();) + for (auto iter = ui_build_selector->errors.begin(); + iter != ui_build_selector->errors.end();) { - //FIXME Hide bags - if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") || - (*iter)->find("No access") != string::npos) - { + // FIXME Hide bags + if (((*iter)->find("Needs") != string::npos + && **iter != "Needs adjacent wall") + || (*iter)->find("No access") != string::npos) iter = ui_build_selector->errors.erase(iter); - } else - { ++iter; - } } } INTERPOSE_NEXT(render)(); + if (!plannable) + return; + auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; - auto type = ui_build_selector->building_type; - if (plannable) + + if (in_dummy_screen) { - if (quickfort_mode && in_dummy_screen) - { - Screen::Pen pen(' ',COLOR_BLACK); - int y = dims.y1 + 1; - Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); + Screen::Pen pen(' ',COLOR_BLACK); + int y = dims.y1 + 1; + Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); - ++y; + ++y; - OutputString(COLOR_BROWN, x, y, "Quickfort Placeholder", true, left_margin); - OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); - } - else - { - int y = 23; + OutputString(COLOR_BROWN, x, y, + "Placeholder for legacy Quickfort. This screen is not required for DFHack native quickfort.", + true, left_margin); + OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); + return; + } - if (show_help) - { - OutputString(COLOR_BROWN, x, y, "Note: "); - OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); - } - OutputToggleString(x, y, "Planning Mode", "P", is_planmode_enabled(type), true, left_margin); + int y = 23; - if (is_planmode_enabled(type)) - { - OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); + OutputToggleString(x, y, "Planning Mode", "P", planmode_enabled[key], true, left_margin); + OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); - auto filter = planner.getDefaultItemFilterForType(type); + if (!is_planmode_enabled(key)) + return; - OutputHotkeyString(x, y, "Min Quality: ", "qw"); - OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); + auto filter = planner.getItemFilters(key).rbegin(); + y += 2; + OutputHotkeyString(x, y, "Min Quality: ", "qw"); + OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); - OutputHotkeyString(x, y, "Max Quality: ", "QW"); - OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); + OutputHotkeyString(x, y, "Max Quality: ", "QW"); + OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); - OutputToggleString(x, y, "Decorated Only: ", "D", filter->decorated_only, true, left_margin); + OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin); - OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); - auto filter_descriptions = filter->getMaterialFilterAsVector(); - for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it) - OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); - } - else - { - in_dummy_screen = false; - } - } - } - else if (isInPlannedBuildingQueryMode()) + OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); + auto filter_descriptions = filter->getMaterials(); + for (auto it = filter_descriptions.begin(); + it != filter_descriptions.end(); ++it) + OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); + } +}; + +struct buildingplan_room_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + std::vector getNoblePositionOfSelectedBuildingOwner() + { + std::vector np; + if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding || + !world->selected_building || + !world->selected_building->owner) { - in_dummy_screen = false; - - // Hide suspend toggle option - int y = 20; - Screen::Pen pen(' ', COLOR_BLACK); - Screen::fillRect(pen, x, y, dims.menu_x2, y); - - auto filter = planner.getSelectedPlannedBuilding()->getFilter(); - y = 24; - OutputString(COLOR_BROWN, x, y, "Planned Building Filter:", true, left_margin); - OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter->getMinQuality(), true, left_margin); - OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter->getMaxQuality(), true, left_margin); - - if (filter->decorated_only) - OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); - - OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); - auto filters = filter->getMaterialFilterAsVector(); - for (auto it = filters.begin(); it != filters.end(); ++it) - OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); + return np; } - else if (isInNobleRoomQueryMode()) + + switch (world->selected_building->getType()) { - auto np = getNoblePositionOfSelectedBuildingOwner(); - int y = 24; - OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); - OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); - for (size_t i = 0; i < np.size() && i < 9; i++) - { - bool enabled = (roomMonitor.getReservedNobleCode(world->selected_building->id) - == np[i].position->code); - OutputToggleString(x, y, np[i].position->name[0].c_str(), - int_to_string(i+1).c_str(), enabled, true, left_margin); - } + case building_type::Bed: + case building_type::Chair: + case building_type::Table: + break; + default: + return np; } + + return getUniqueNoblePositions(world->selected_building->owner); + } + + bool isInNobleRoomQueryMode() + { + if (getNoblePositionOfSelectedBuildingOwner().size() > 0) + return canReserveRoom(world->selected_building); else + return false; + } + + bool handleInput(set *input) + { + if (!isInNobleRoomQueryMode()) + return false; + + if (Gui::inRenameBuilding()) + return false; + auto np = getNoblePositionOfSelectedBuildingOwner(); + df::interface_key last_token = get_string_key(input); + if (last_token >= interface_key::STRING_A048 + && last_token <= interface_key::STRING_A058) + { + size_t selection = last_token - interface_key::STRING_A048; + if (np.size() < selection) + return false; + roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (!isInNobleRoomQueryMode()) + return; + + auto np = getNoblePositionOfSelectedBuildingOwner(); + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 24; + OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); + OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); + for (size_t i = 0; i < np.size() && i < 9; i++) { - in_dummy_screen = false; - show_help = false; + bool enabled = + roomMonitor.getReservedNobleCode(world->selected_building->id) + == np[i].position->code; + OutputToggleString(x, y, np[i].position->name[0].c_str(), + int_to_string(i+1).c_str(), enabled, true, left_margin); } } }; -IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render); static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { @@ -624,10 +638,14 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { - planner.reset(out); - - if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) || - !INTERPOSE_HOOK(buildingplan_hook, render).apply(enable)) + planner.reset(); + + if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_room_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_query_hook, render).apply(enable) || + !INTERPOSE_HOOK(buildingplan_place_hook, render).apply(enable) || + !INTERPOSE_HOOK(buildingplan_room_hook, render).apply(enable)) return CR_FAILURE; is_enabled = enable; @@ -640,7 +658,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector frame_counter % (DAY_TICKS/2) == 0) + if (Maps::IsValid() && !World::ReadPauseState() + && (cycle_requested || world->frame_counter % (DAY_TICKS/2) == 0)) { planner.doCycle(); roomMonitor.doCycle(); + cycle_requested = false; } return CR_OK; @@ -680,8 +702,11 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) // Lua API section -static bool isPlannableBuilding(df::building_type type) { - return planner.isPlannableBuilding(type); +static bool isPlannableBuilding(df::building_type type, + int16_t subtype, + int32_t custom) { + return planner.isPlannableBuilding( + toBuildingTypeKey(type, subtype, custom)); } static void addPlannedBuilding(df::building *bld) { @@ -692,9 +717,14 @@ static void doCycle() { planner.doCycle(); } +static void scheduleCycle() { + cycle_requested = true; +} + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isPlannableBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding), DFHACK_LUA_FUNCTION(doCycle), + DFHACK_LUA_FUNCTION(scheduleCycle), DFHACK_LUA_END }; diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 72d11b78a..ff8aa547f 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -48,7 +48,7 @@ struct BuildingInfo { } bool allocate() { - return planner.allocatePlannedBuilding(type); + return planner.allocatePlannedBuilding(toBuildingTypeKey(type, -1, -1)); } }; @@ -122,7 +122,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { - planner.reset(out); + planner.reset(); is_enabled = enable; } diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 071c07395..157b6211b 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -4,9 +4,10 @@ local _ENV = mkmodule('plugins.buildingplan') Native functions: - * bool isPlannableBuilding(df::building_type type) + * bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) * void addPlannedBuilding(df::building *bld) * void doCycle() + * void scheduleCycle() --]] diff --git a/plugins/uicommon.h b/plugins/uicommon.h index d8624786c..78256ac69 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -79,7 +79,7 @@ static void for_each_(map &v, Fn func) } template -static void transform_(vector &src, vector &dst, Fn func) +static void transform_(const vector &src, vector &dst, Fn func) { transform(src.begin(), src.end(), back_inserter(dst), func); } From 5087b6d2ae88efcac5efef7943daddd5d8cba754 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 4 Oct 2020 23:53:42 -0700 Subject: [PATCH 004/112] use new default_item_filters structure so vector references aren't to items on the stack also don't let query mode swallow all input --- plugins/buildingplan-planner.cpp | 8 ++++---- plugins/buildingplan-planner.h | 4 +++- plugins/buildingplan.cpp | 7 +++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index c367ecc33..25df38ad5 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -439,7 +439,7 @@ void Planner::initialize() void Planner::reset() { debug("resetting Planner state"); - item_filters.clear(); + default_item_filters.clear(); planned_buildings.clear(); std::vector items; @@ -549,9 +549,9 @@ Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) int nfilters = getNumFilters(key); if (nfilters < 1) return empty_ret; - std::vector ret; - ret.push_back(item_filters[std::get<0>(key)]); - return ItemFiltersWrapper(ret); + while (default_item_filters[key].size() < nfilters) + default_item_filters[key].push_back(ItemFilter()); + return ItemFiltersWrapper(default_item_filters[key]); } void Planner::doCycle() diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index a1f53dcd2..9341ad03f 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -119,7 +119,9 @@ public: private: std::map item_for_building_type; - std::map item_filters; + std::unordered_map, + BuildingTypeKeyHash> default_item_filters; std::map> available_item_vectors; std::map is_relevant_item_type; //Needed for fast check when looping over all items std::vector planned_buildings; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 6cb45c879..2caa9c542 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -309,13 +309,12 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest return true; // Don't unsuspend planned buildings if (input->count(interface_key::DESTROYBUILDING)) { - // remove persistent data + // remove persistent data and allow the parent to handle the key + // so the building is removed planner.getPlannedBuilding(world->selected_building)->remove(); - // still allow the building to be removed - return false; } - return true; + return false; } DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) From 9116b331b7805b2902bf623daa4d7464e96c2b48 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 5 Oct 2020 15:14:39 -0700 Subject: [PATCH 005/112] fix compiler warnings --- plugins/buildingplan-planner.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 25df38ad5..d22f7f0b8 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -1,5 +1,6 @@ #include "df/building_design.h" #include "df/building_doorst.h" +#include "df/building_type.h" #include "df/general_ref_building_holderst.h" #include "df/job_item.h" #include "df/ui_build_selector.h" @@ -236,15 +237,15 @@ static std::vector deserializeFilters(PersistentDataItem &config) return ret; } -static int getNumFilters(BuildingTypeKey key) +static size_t getNumFilters(BuildingTypeKey key) { // TODO: get num filters in Lua when we handle all building types return 1; } PlannedBuilding::PlannedBuilding(df::building *building, const std::vector &filters) - : building(building), - building_id(building->id), + : building_id(building->id), + building(building), filters(filters) { config = DFHack::World::AddPersistentData(planned_building_persistence_key_v1); @@ -546,7 +547,7 @@ Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) static std::vector empty_vector; static const ItemFiltersWrapper empty_ret(empty_vector); - int nfilters = getNumFilters(key); + size_t nfilters = getNumFilters(key); if (nfilters < 1) return empty_ret; while (default_item_filters[key].size() < nfilters) From efd68a3bc9327769b6cad54cee927d8a3f5d8690 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 6 Oct 2020 08:23:16 -0700 Subject: [PATCH 006/112] reinstate buildingplan capital hotkey reminder --- plugins/buildingplan.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 2caa9c542..f94a4bbcf 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -28,6 +28,7 @@ REQUIRE_GLOBAL(world); using namespace DFHack; using namespace df::enums; +bool show_help = false; bool quickfort_mode = false; bool in_dummy_screen = false; std::unordered_map planmode_enabled; @@ -373,7 +374,10 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { if (!isInPlannedBuildingPlacementMode()) + { + show_help = false; return false; + } if (in_dummy_screen) { @@ -389,6 +393,14 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest return true; } + if (input->count(interface_key::CUSTOM_P) || + input->count(interface_key::CUSTOM_F) || + input->count(interface_key::CUSTOM_D) || + input->count(interface_key::CUSTOM_M)) + { + show_help = true; + } + BuildingTypeKey key = toBuildingTypeKey(ui_build_selector); if (input->count(interface_key::CUSTOM_SHIFT_P)) { @@ -491,6 +503,12 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest int y = 23; + if (show_help) + { + OutputString(COLOR_BROWN, x, y, "Note: "); + OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); + } + OutputToggleString(x, y, "Planning Mode", "P", planmode_enabled[key], true, left_margin); OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); From 0edebd7e1f351b5345a8b32e2434c7f981762263 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 6 Oct 2020 08:24:59 -0700 Subject: [PATCH 007/112] attempt to fix build errors on gcc-4.8 --- plugins/buildingplan-planner.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index d22f7f0b8..34a377555 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -1,3 +1,5 @@ +#include + #include "df/building_design.h" #include "df/building_doorst.h" #include "df/building_type.h" @@ -244,8 +246,8 @@ static size_t getNumFilters(BuildingTypeKey key) } PlannedBuilding::PlannedBuilding(df::building *building, const std::vector &filters) - : building_id(building->id), - building(building), + : building(building), + building_id(building->id), filters(filters) { config = DFHack::World::AddPersistentData(planned_building_persistence_key_v1); @@ -256,8 +258,8 @@ PlannedBuilding::PlannedBuilding(df::building *building, const std::vector Date: Tue, 6 Oct 2020 09:39:21 -0700 Subject: [PATCH 008/112] work around gcc-4-8's missing full std::hash impl and improve our hash function while I'm there --- plugins/buildingplan-planner.cpp | 20 +++++++++++++++++--- plugins/buildingplan.cpp | 3 --- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 34a377555..3cb27145e 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -1,4 +1,5 @@ #include +#include // for CHAR_BIT #include "df/building_design.h" #include "df/building_doorst.h" @@ -386,11 +387,24 @@ BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs) uibs->building_type, uibs->building_subtype, uibs->custom_type); } +// rotates a size_t value left by count bits +// assumes count is not 0 or >= size_t_bits +// replace this with std::rotl when we move to C++20 +static std::size_t rotl_size_t(size_t val, uint32_t count) +{ + static const int size_t_bits = CHAR_BIT * sizeof(std::size_t); + return val << count | val >> (size_t_bits - count); +} + std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const { - return std::hash()(std::get<0>(key)) - ^ std::hash()(std::get<1>(key)) - ^ std::hash()(std::get<2>(key)); + // cast first param to appease gcc-4.8, which is missing the enum + // specializations for std::hash + std::size_t h1 = std::hash()(static_cast(std::get<0>(key))); + std::size_t h2 = std::hash()(std::get<1>(key)); + std::size_t h3 = std::hash()(std::get<2>(key)); + + return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16); } diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index f94a4bbcf..1d25da6f0 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -25,9 +25,6 @@ REQUIRE_GLOBAL(world); #define MAX_MASK 10 #define MAX_MATERIAL 21 -using namespace DFHack; -using namespace df::enums; - bool show_help = false; bool quickfort_mode = false; bool in_dummy_screen = false; From 93520b4b004b44b01f59cd7616f0315e4bfa8b43 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 11 Oct 2020 20:45:56 -0400 Subject: [PATCH 009/112] dwarfmonitor prefs: fix segfault if item_subtype is null for some item types --- docs/changelog.txt | 3 ++ plugins/dwarfmonitor.cpp | 85 +++++++++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index faec636dd..7f289c36d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +## Fixes +- `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences + # 0.47.04-r3 ## New Plugins diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index fc1679a19..84e9b9abe 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1155,52 +1155,107 @@ struct preference_map string getItemLabel() { - df::world_raws::T_itemdefs &defs = world->raws.itemdefs; label = ENUM_ATTR_STR(item_type, caption, pref.item_type); switch (pref.item_type) { case (df::item_type::WEAPON): - label = defs.weapons[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.weapons, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::TRAPCOMP): - label = defs.trapcomps[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.trapcomps, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::TOY): - label = defs.toys[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.toys, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::TOOL): - label = defs.tools[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.tools, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::INSTRUMENT): - label = defs.instruments[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.instruments, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::ARMOR): - label = defs.armor[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.armor, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::AMMO): - label = defs.ammo[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.ammo, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::SIEGEAMMO): - label = defs.siege_ammo[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.siege_ammo, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::GLOVES): - label = defs.gloves[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.gloves, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::SHOES): - label = defs.shoes[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.shoes, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::SHIELD): - label = defs.shields[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.shields, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::HELM): - label = defs.helms[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.helms, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::PANTS): - label = defs.pants[pref.item_subtype]->name_plural; + { + auto *def = vector_get(world->raws.itemdefs.pants, pref.item_subtype); + if (def) + label = def->name_plural; break; + } case (df::item_type::FOOD): - label = defs.food[pref.item_subtype]->name; + { + auto *def = vector_get(world->raws.itemdefs.food, pref.item_subtype); + if (def) + label = def->name; break; + } default: label = ENUM_ATTR_STR(item_type, caption, pref.item_type); From bdf2bbc22e587dddc0ab63487059bd41b6132841 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 11 Oct 2020 22:15:01 -0700 Subject: [PATCH 010/112] remove reference in docs to unimplemented behavior I never implemented ignoring spaces in alias definitions, and now that I've thought more about the consequences, I don't plan to : ) Ignoring spaces would lead to conusing behavior when players attempt to give names with spaces to buildings --- dfhack-config/quickfort/aliases.txt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt index c5d34ec39..2b90e67c4 100644 --- a/dfhack-config/quickfort/aliases.txt +++ b/dfhack-config/quickfort/aliases.txt @@ -22,12 +22,12 @@ # # For example, say you have the following build and place blueprints: # -# #build start(4;1;upper left corner of stockpile) build masonry workshop +# #build masonry workshop # ~, ~,~,`,`,` # ~,wm,~,`,`,` # ~, ~,~,`,`,` # -# #place start(4;1;upper left corner of stockpile) place stockpile for mason +# #place stockpile for mason # ~,~,~,s,s,s # ~,~,~,s,s,s # ~,~,~,s,s,s @@ -63,9 +63,7 @@ # keycode from the DF interface definition file (data/init/interface.txt), # enclosed in curly brackets like an alias, like: "{Right}" or "{Enter}". In # order to avoid naming conflicts between aliases and keycodes, the convention -# is to start aliases with a lowercase letter. You can add spaces in between -# keystrokes to make them easier to read. Spaces in keystroke sequences will be -# ignored. To insert a literal space, use "{Space}" +# is to start aliases with a lowercase letter. # # Anything enclosed within curly brackets can also have a number after it, # indicating how many times that alias or keycode should be repeated. For From f754164a1ddaa2e0c8b63a7349e4da1b3347fd9a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 11 Oct 2020 22:16:42 -0700 Subject: [PATCH 011/112] document new query_unsafe option --- dfhack-config/quickfort/quickfort.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dfhack-config/quickfort/quickfort.txt b/dfhack-config/quickfort/quickfort.txt index 575b7a804..c1d70201f 100644 --- a/dfhack-config/quickfort/quickfort.txt +++ b/dfhack-config/quickfort/quickfort.txt @@ -22,6 +22,14 @@ buildings_use_blocks=true # be designated in marker mode. force_marker_mode=false +# Skip query blueprint sanity checks that detect common blueprint errors and +# halt or skip keycode playback. Checks include ensuring a configurable building +# exists at the designated cursor position and verifying the active UI screen is +# the same before and after sending keys for the cursor position. Temporarily +# enable this if you are running a query blueprint that sends a key sequence +# that is *not* related to stockpile or building configuration. +query_unsafe=false + # Set to the maximum number of resources you want assigned to stockpiles of the # relevant types. Set to -1 for DF defaults (number of stockpile tiles for # stockpiles that take barrels and bins, 1 wheelbarrow for stone stockpiles). From 88fd25e63c1631fd363748d68176f76d9a204c6e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 12 Oct 2020 23:51:51 -0400 Subject: [PATCH 012/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 0b2748554..a574158ca 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 0b274855424e5d0850d2cfc8b10e1cdcc47c6877 +Subproject commit a574158ca3f12ed2911e77632e09972be56beb3a From cc159909e2b069b0a7adf66e037b6a1eee998001 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 13 Oct 2020 00:24:29 -0400 Subject: [PATCH 013/112] Mention some third-party Linux packages Closes #20 --- docs/Installing.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/Installing.rst b/docs/Installing.rst index c0175b820..5b564f45f 100644 --- a/docs/Installing.rst +++ b/docs/Installing.rst @@ -150,3 +150,19 @@ DF, DFHack, and other utilities. If you are new to Dwarf Fortress and DFHack, these may be easier to set up. Note that these packs are not maintained by the DFHack team and vary in their release schedules and contents. Some may make significant configuration changes, and some may not include DFHack at all. + +Linux packages +============== + +Third-party DFHack packages are available for some Linux distributions, +including in: + +* `AUR `__, for Arch and related + distributions +* `RPM Fusion `__, + for Fedora and related distributions + +Note that these may lag behind DFHack releases. If you want to use a newer +version of DFHack, we generally recommended installing it in a clean copy of DF +in your home folder. Attempting to upgrade an installation of DFHack from a +package manager may break it. From ce7772a1c267bdba1f2c305a872fe8b088302a51 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Oct 2020 21:22:53 -0400 Subject: [PATCH 014/112] Add Filesystem::restorecwd() This allows restoring the working directory to its original value, which may not actually be the DF root. See #1671, dfhack/scripts#152 --- docs/Lua API.rst | 4 ++++ library/Core.cpp | 5 ++++- library/LuaApi.cpp | 1 + library/include/modules/Filesystem.h | 2 ++ library/modules/Filesystem.cpp | 17 +++++++++++++++++ 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fb67610d1..b310b449e 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -2160,6 +2160,10 @@ unless otherwise noted. Changes the current directory to ``path``. Use with caution. +* ``dfhack.filesystem.restorecwd()`` + + Restores the current working directory to what it was when DF started. + * ``dfhack.filesystem.mkdir(path)`` Creates a new directory. Returns ``false`` if unsuccessful, including if ``path`` already exists. diff --git a/library/Core.cpp b/library/Core.cpp index c6ce9ea48..ff4ccc98e 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1618,7 +1618,10 @@ bool Core::Init() freopen("stderr.log", "w", stderr); #endif - fprintf(stderr, "DFHack build: %s\n", Version::git_description()); + Filesystem::init(); + + cerr << "DFHack build: " << Version::git_description() << "\n" + << "Starting with working directory: " << Filesystem::getcwd() << endl; // find out what we are... #ifdef LINUX_BUILD diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index c863a8d4d..d38cb31ba 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2371,6 +2371,7 @@ static const luaL_Reg dfhack_screen_funcs[] = { static const LuaWrapper::FunctionReg dfhack_filesystem_module[] = { WRAPM(Filesystem, getcwd), + WRAPM(Filesystem, restorecwd), WRAPM(Filesystem, chdir), WRAPM(Filesystem, mkdir), WRAPM(Filesystem, mkdir_recursive), diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h index c9218ba33..eac8a1102 100644 --- a/library/include/modules/Filesystem.h +++ b/library/include/modules/Filesystem.h @@ -146,8 +146,10 @@ enum _filetype { namespace DFHack { namespace Filesystem { + DFHACK_EXPORT void init (); DFHACK_EXPORT bool chdir (std::string path); DFHACK_EXPORT std::string getcwd (); + DFHACK_EXPORT bool restorecwd (); DFHACK_EXPORT bool mkdir (std::string path); // returns true on success or if directory already exists DFHACK_EXPORT bool mkdir_recursive (std::string path); diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index c0d0860ad..7738bfd5e 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -52,6 +52,18 @@ SOFTWARE. using namespace DFHack; +static bool initialized = false; +static std::string initial_cwd; + +void Filesystem::init () +{ + if (!initialized) + { + initialized = true; + initial_cwd = Filesystem::getcwd(); + } +} + bool Filesystem::chdir (std::string path) { return ::chdir(path.c_str()) == 0; @@ -71,6 +83,11 @@ std::string Filesystem::getcwd () return result; } +bool Filesystem::restorecwd () +{ + return Filesystem::chdir(initial_cwd); +} + bool Filesystem::mkdir (std::string path) { int fail; From 82013c0c5ea145ef709048e0d130f9a184ffbd48 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 16 Oct 2020 13:52:23 -0700 Subject: [PATCH 015/112] prep buildingplan for core algorithm changes Lots of refactoring and reorganizing, with only cosmetic player-visible changes. - show quickfort mode hotlkey label regardless of whether the current building type has buildingplan enabled. before, it was only shown after the user enabled buildingplan for the current building. this eliminates the extra step when enabling quickfort mode, which force-enables all building types. - changed signature of lua-exported isPlannableBuilding to take subtype and custom type in addition to building type. this is only used by quickfort, and it already sends all three params in preparation for this change - added lua-exported scheduleCycle(), which is like doCycle(), but only takes effect on the next non-paused frame. this lets quickfort run only one buildingplan cycle regardless of how many #build blueprints were run - declared a few dfhack library methods and params const so buildingplan could call them from const methods - converted buildingplan internal debug logging fn to have a printf api - reshaped buildingplan-planner API and refactored implementation in preparation for upcoming core algorithm changes for supporing all building types (no externally-visible functionality changes) - changed df::building_type params to type, subtype, custom tuple keys - introduced capability to return multiple filters per building type (though the current buildings all only have one filter per) - split monolith hook functions in buildingplan.cpp into one per scope. this significantly cleans up the code and preps the hooks to handle iterating through multiple item filters. - got rid of send_key function and replaced with better reporting of whether keys have been handled --- docs/Lua API.rst | 3 +- library/include/modules/Materials.h | 4 +- library/modules/Materials.cpp | 4 +- plugins/buildingplan-lib.cpp | 10 +- plugins/buildingplan-lib.h | 2 +- plugins/buildingplan-planner.cpp | 549 ++++++++++++++++----------- plugins/buildingplan-planner.h | 132 ++++--- plugins/buildingplan.cpp | 554 +++++++++++++++------------- plugins/fortplan.cpp | 4 +- plugins/lua/buildingplan.lua | 3 +- plugins/uicommon.h | 2 +- 11 files changed, 737 insertions(+), 530 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fb67610d1..86470887c 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3702,9 +3702,10 @@ buildingplan Native functions: -* ``bool isPlannableBuilding(df::building_type type)`` returns whether the building type is handled by buildingplan +* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan * ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list * ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. +* ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. burrows ======= diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index b326719c6..a3b4204e5 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -136,8 +136,8 @@ namespace DFHack return findProduct(info.material, name); } - std::string getToken(); - std::string toString(uint16_t temp = 10015, bool named = true); + std::string getToken() const; + std::string toString(uint16_t temp = 10015, bool named = true) const; bool isAnyCloth(); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index c341ea99e..909fa56f9 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -305,7 +305,7 @@ bool MaterialInfo::findProduct(df::material *material, const std::string &name) return decode(-1); } -std::string MaterialInfo::getToken() +std::string MaterialInfo::getToken() const { if (isNone()) return "NONE"; @@ -333,7 +333,7 @@ std::string MaterialInfo::getToken() } } -std::string MaterialInfo::toString(uint16_t temp, bool named) +std::string MaterialInfo::toString(uint16_t temp, bool named) const { if (isNone()) return "any"; diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index 215dbb900..e05d9b9dc 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -1,16 +1,22 @@ #include "buildingplan-lib.h" +#include #include "Core.h" using namespace DFHack; bool show_debugging = false; -void debug(const std::string &msg) +void debug(const char *fmt, ...) { if (!show_debugging) return; color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG: " << msg << endl; + out.print("DEBUG(buildingplan): "); + va_list args; + va_start(args, fmt); + out.vprint(fmt, args); + va_end(args); + out.print("\n"); } diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index 61a00e796..e906ef1a7 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -3,6 +3,6 @@ #include "buildingplan-planner.h" #include "buildingplan-rooms.h" -void debug(const std::string &msg); +void debug(const char *fmt, ...) Wformat(printf,1,2); extern bool show_debugging; diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 6923570c2..3cb27145e 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -1,185 +1,269 @@ +#include +#include // for CHAR_BIT + +#include "df/building_design.h" +#include "df/building_doorst.h" +#include "df/building_type.h" #include "df/general_ref_building_holderst.h" #include "df/job_item.h" -#include "df/building_doorst.h" -#include "df/building_design.h" +#include "df/ui_build_selector.h" -#include "modules/Job.h" #include "modules/Buildings.h" #include "modules/Gui.h" +#include "modules/Job.h" + +#include "uicommon.h" #include "buildingplan-planner.h" #include "buildingplan-lib.h" -#include "uicommon.h" -/* -* ItemFilter -*/ +static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints"; -ItemFilter::ItemFilter() : - min_quality(df::item_quality::Ordinary), - max_quality(df::item_quality::Artifact), - decorated_only(false), - valid(true) -{ - clear(); // mat_mask is not cleared by default (see issue #1047) -} +/* + * ItemFilter + */ -bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) +ItemFilter::ItemFilter() { - return (mat_mask.whole) ? mat.matches(mat_mask) : true; + clear(); } -bool ItemFilter::matches(const df::dfhack_material_category mask) const +void ItemFilter::clear() { - return mask.whole & mat_mask.whole; + min_quality = df::item_quality::Ordinary; + max_quality = df::item_quality::Masterful; + decorated_only = false; + clearMaterialMask(); + materials.clear(); } -bool ItemFilter::matches(DFHack::MaterialInfo &material) const +bool ItemFilter::deserialize(PersistentDataItem &config) { - for (auto it = materials.begin(); it != materials.end(); ++it) - if (material.matches(*it)) - return true; - return false; -} + clear(); -bool ItemFilter::matches(df::item *item) -{ - if (item->getQuality() < min_quality || item->getQuality() > max_quality) + std::vector tokens; + split_string(&tokens, config.val(), "/"); + if (tokens.size() != 2) + { + debug("invalid ItemFilter serialization: '%s'", config.val().c_str()); return false; + } - if (decorated_only && !item->hasImprovements()) + if (!deserializeMaterialMask(tokens[0]) || !deserializeMaterials(tokens[1])) return false; - auto imattype = item->getActualMaterial(); - auto imatindex = item->getActualMaterialIndex(); - auto item_mat = DFHack::MaterialInfo(imattype, imatindex); - - return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); + setMinQuality(config.ival(2) - 1); + setMaxQuality(config.ival(4) - 1); + decorated_only = config.ival(3) - 1; + return true; } -std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } +bool ItemFilter::deserializeMaterialMask(std::string ser) +{ + if (ser.empty()) + return true; -std::vector ItemFilter::getMaterialFilterAsVector() + if (!parseJobMaterialCategory(&mat_mask, ser)) + { + debug("invalid job material category serialization: '%s'", ser.c_str()); + return false; + } + return true; +} + +bool ItemFilter::deserializeMaterials(std::string ser) { - std::vector descriptions; + if (ser.empty()) + return true; - transform_(materials, descriptions, material_to_string_fn); + std::vector mat_names; + split_string(&mat_names, ser, ","); + for (auto m = mat_names.begin(); m != mat_names.end(); m++) + { + DFHack::MaterialInfo material; + if (!material.find(*m) || !material.isValid()) + { + debug("invalid material name serialization: '%s'", ser.c_str()); + return false; + } + materials.push_back(material); + } + return true; +} - if (descriptions.size() == 0) - bitfield_to_string(&descriptions, mat_mask); +void ItemFilter::serialize(PersistentDataItem &config) const +{ + std::ostringstream ser; + ser << bitfield_to_string(mat_mask, ",") << "/"; + if (!materials.empty()) + { + ser << materials[0].getToken(); + for (size_t i = 1; i < materials.size(); ++i) + ser << "," << materials[i].getToken(); + } + config.val() = ser.str(); + config.ival(2) = min_quality + 1; + config.ival(4) = max_quality + 1; + config.ival(3) = static_cast(decorated_only) + 1; +} - if (descriptions.size() == 0) - descriptions.push_back("any"); +void ItemFilter::clearMaterialMask() +{ + mat_mask.whole = 0; +} - return descriptions; +void ItemFilter::addMaterialMask(uint32_t mask) +{ + mat_mask.whole |= mask; } -std::string ItemFilter::getMaterialFilterAsSerial() +void ItemFilter::setMaterials(std::vector materials) { - std::string str; + this->materials = materials; +} - str.append(bitfield_to_string(mat_mask, ",")); - str.append("/"); - if (materials.size() > 0) +static void clampItemQuality(df::item_quality *quality) +{ + if (*quality > item_quality::Artifact) { - for (size_t i = 0; i < materials.size(); i++) - str.append(materials[i].getToken() + ","); - - if (str[str.size()-1] == ',') - str.resize(str.size () - 1); + debug("clamping quality to Artifact"); + *quality = item_quality::Artifact; } + if (*quality < item_quality::Ordinary) + { + debug("clamping quality to Ordinary"); + *quality = item_quality::Ordinary; + } +} - return str; +void ItemFilter::setMinQuality(int quality) +{ + min_quality = static_cast(quality); + clampItemQuality(&min_quality); + if (max_quality < min_quality) + max_quality = min_quality; } -bool ItemFilter::parseSerializedMaterialTokens(std::string str) +void ItemFilter::setMaxQuality(int quality) { - valid = false; - std::vector tokens; - split_string(&tokens, str, "/"); + max_quality = static_cast(quality); + clampItemQuality(&max_quality); + if (max_quality < min_quality) + min_quality = max_quality; +} - if (tokens.size() > 0 && !tokens[0].empty()) - { - if (!parseJobMaterialCategory(&mat_mask, tokens[0])) - return false; - } +void ItemFilter::incMinQuality() { setMinQuality(min_quality + 1); } +void ItemFilter::decMinQuality() { setMinQuality(min_quality - 1); } +void ItemFilter::incMaxQuality() { setMaxQuality(max_quality + 1); } +void ItemFilter::decMaxQuality() { setMaxQuality(max_quality - 1); } - if (tokens.size() > 1 && !tokens[1].empty()) - { - std::vector mat_names; - split_string(&mat_names, tokens[1], ","); - for (auto m = mat_names.begin(); m != mat_names.end(); m++) - { - DFHack::MaterialInfo material; - if (!material.find(*m) || !material.isValid()) - return false; +void ItemFilter::toggleDecoratedOnly() { decorated_only = !decorated_only; } - materials.push_back(material); - } - } +static std::string material_to_string_fn(const MaterialInfo &m) { return m.toString(); } - valid = true; - return true; +uint32_t ItemFilter::getMaterialMask() const { return mat_mask.whole; } + +std::vector ItemFilter::getMaterials() const +{ + std::vector descriptions; + transform_(materials, descriptions, material_to_string_fn); + + if (descriptions.size() == 0) + bitfield_to_string(&descriptions, mat_mask); + + if (descriptions.size() == 0) + descriptions.push_back("any"); + + return descriptions; } -std::string ItemFilter::getMinQuality() +std::string ItemFilter::getMinQuality() const { return ENUM_KEY_STR(item_quality, min_quality); } -std::string ItemFilter::getMaxQuality() +std::string ItemFilter::getMaxQuality() const { return ENUM_KEY_STR(item_quality, max_quality); } -bool ItemFilter::isValid() +bool ItemFilter::getDecoratedOnly() const { - return valid; + return decorated_only; } -void ItemFilter::clear() +bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) const { - mat_mask.whole = 0; - materials.clear(); + return mat_mask.whole ? mat.matches(mat_mask) : true; } -/* -* PlannedBuilding -*/ +bool ItemFilter::matches(df::dfhack_material_category mask) const +{ + return mask.whole & mat_mask.whole; +} -PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) +bool ItemFilter::matches(DFHack::MaterialInfo &material) const { - this->building = building; - this->filter = *filter; - pos = df::coord(building->centerx, building->centery, building->z); - config = DFHack::World::AddPersistentData("buildingplan/constraints"); - config.val() = filter->getMaterialFilterAsSerial(); - config.ival(1) = building->id; - config.ival(2) = filter->min_quality + 1; - config.ival(3) = static_cast(filter->decorated_only) + 1; - config.ival(4) = filter->max_quality + 1; + for (auto it = materials.begin(); it != materials.end(); ++it) + if (material.matches(*it)) + return true; + return false; } -PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out) +bool ItemFilter::matches(df::item *item) const { - this->config = config; + if (item->getQuality() < min_quality || item->getQuality() > max_quality) + return false; - if (!filter.parseSerializedMaterialTokens(config.val())) - { - out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); - return; - } + if (decorated_only && !item->hasImprovements()) + return false; - building = df::building::find(config.ival(1)); - if (!building) - return; + auto imattype = item->getActualMaterial(); + auto imatindex = item->getActualMaterialIndex(); + auto item_mat = DFHack::MaterialInfo(imattype, imatindex); + + return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); +} + + +/* + * PlannedBuilding + */ - pos = df::coord(building->centerx, building->centery, building->z); - filter.min_quality = static_cast(config.ival(2) - 1); - filter.max_quality = static_cast(config.ival(4) - 1); - filter.decorated_only = config.ival(3) - 1; +static std::vector deserializeFilters(PersistentDataItem &config) +{ + // simplified implementation while we can assume there is only one filter + std::vector ret; + ItemFilter itemFilter; + itemFilter.deserialize(config); + ret.push_back(itemFilter); + return ret; +} + +static size_t getNumFilters(BuildingTypeKey key) +{ + // TODO: get num filters in Lua when we handle all building types + return 1; +} + +PlannedBuilding::PlannedBuilding(df::building *building, const std::vector &filters) + : building(building), + building_id(building->id), + filters(filters) +{ + config = DFHack::World::AddPersistentData(planned_building_persistence_key_v1); + config.ival(1) = building_id; + // assume all filter vectors are length 1 for now + filters[0].serialize(config); } +PlannedBuilding::PlannedBuilding(PersistentDataItem &config) + : config(config), + building(df::building::find(config.ival(1))), + building_id(config.ival(1)), + filters(deserializeFilters(config)) +{ } + bool PlannedBuilding::assignClosestItem(std::vector *items_vector) { decltype(items_vector->begin()) closest_item; @@ -187,7 +271,7 @@ bool PlannedBuilding::assignClosestItem(std::vector *items_vector) for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) { auto item = *item_iter; - if (!filter.matches(item)) + if (!filters[0].matches(item)) continue; auto pos = item->pos; @@ -255,68 +339,83 @@ bool PlannedBuilding::assignItem(df::item *item) return true; } -bool PlannedBuilding::isValid() +// Ensure the building still exists and is in a valid state. It can disappear +// for lots of reasons, such as running the game with the buildingplan plugin +// disabled, manually removing the building, modifying it via the API, etc. +bool PlannedBuilding::isValid() const { - bool valid = filter.isValid() && - building && Buildings::findAtTile(pos) == building && - building->getBuildStage() == 0; - - if (!valid) - remove(); - - return valid; + return building && df::building::find(building_id) + && building->getBuildStage() == 0; } -df::building_type PlannedBuilding::getType() +void PlannedBuilding::remove() { - return building->getType(); + DFHack::World::DeletePersistentData(config); + building = NULL; } -bool PlannedBuilding::isCurrentlySelectedBuilding() +df::building * PlannedBuilding::getBuilding() { - return isValid() && (building == df::global::world->selected_building); + return building; } -ItemFilter *PlannedBuilding::getFilter() +const std::vector & PlannedBuilding::getFilters() const { - return &filter; + return filters; } -void PlannedBuilding::remove() + +/* + * BuildingTypeKey + */ + +BuildingTypeKey toBuildingTypeKey( + df::building_type btype, int16_t subtype, int32_t custom) { - DFHack::World::DeletePersistentData(config); + return std::make_tuple(btype, subtype, custom); } -/* -* Planner -*/ +BuildingTypeKey toBuildingTypeKey(df::building *bld) +{ + return std::make_tuple( + bld->getType(), bld->getSubtype(), bld->getCustomType()); +} -Planner::Planner() { } +BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs) +{ + return std::make_tuple( + uibs->building_type, uibs->building_subtype, uibs->custom_type); +} -bool Planner::isPlannableBuilding(const df::building_type type) const +// rotates a size_t value left by count bits +// assumes count is not 0 or >= size_t_bits +// replace this with std::rotl when we move to C++20 +static std::size_t rotl_size_t(size_t val, uint32_t count) { - return item_for_building_type.find(type) != item_for_building_type.end(); + static const int size_t_bits = CHAR_BIT * sizeof(std::size_t); + return val << count | val >> (size_t_bits - count); } -void Planner::reset(color_ostream &out) +std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const { - planned_buildings.clear(); - std::vector items; - DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); + // cast first param to appease gcc-4.8, which is missing the enum + // specializations for std::hash + std::size_t h1 = std::hash()(static_cast(std::get<0>(key))); + std::size_t h2 = std::hash()(std::get<1>(key)); + std::size_t h3 = std::hash()(std::get<2>(key)); - for (auto i = items.begin(); i != items.end(); i++) - { - PlannedBuilding pb(*i, out); - if (pb.isValid()) - planned_buildings.push_back(pb); - } + return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16); } + +/* + * Planner + */ + void Planner::initialize() { #define add_building_type(btype, itype) \ item_for_building_type[df::building_type::btype] = df::item_type::itype; \ - default_item_filters[df::building_type::btype] = ItemFilter(); \ available_item_vectors[df::item_type::itype] = std::vector(); \ is_relevant_item_type[df::item_type::itype] = true; \ @@ -354,53 +453,82 @@ void Planner::initialize() #undef add_building_type } -void Planner::addPlannedBuilding(df::building *bld) +void Planner::reset() { - for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++) + debug("resetting Planner state"); + default_item_filters.clear(); + planned_buildings.clear(); + + std::vector items; + DFHack::World::GetPersistentData(&items, planned_building_persistence_key_v1); + debug("found data for %zu planned buildings", items.size()); + + for (auto i = items.begin(); i != items.end(); i++) { - (*iter)->flags.bits.suspend = true; - } + PlannedBuilding pb(*i); + if (!pb.isValid()) + { + pb.remove(); + continue; + } - PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); - planned_buildings.push_back(pb); + planned_buildings.push_back(pb); + } } -void Planner::doCycle() +void Planner::addPlannedBuilding(df::building *bld) { - debug("Running Cycle"); - if (planned_buildings.size() == 0) + auto item_filters = getItemFilters(toBuildingTypeKey(bld)).get(); + // not a supported type + if (item_filters.empty()) + { + debug("failed to add building: unsupported type"); return; + } - debug("Planned count: " + int_to_string(planned_buildings.size())); + // protect against multiple registrations + if (getPlannedBuilding(bld)) + { + debug("building already registered"); + return; + } - gather_available_items(); - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + PlannedBuilding pb(bld, item_filters); + if (pb.isValid()) { - if (building_iter->isValid()) - { - if (show_debugging) - debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); + for (auto job : bld->jobs) + job->flags.bits.suspend = true; - auto required_item_type = item_for_building_type[building_iter->getType()]; - auto items_vector = &available_item_vectors[required_item_type]; - if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) - { - debug("Unable to allocate an item"); - ++building_iter; - continue; - } - } - debug("Removing building plan"); - building_iter = planned_buildings.erase(building_iter); + planned_buildings.push_back(pb); } + else + { + pb.remove(); + } +} + +PlannedBuilding * Planner::getPlannedBuilding(df::building *bld) +{ + for (auto & pb : planned_buildings) + { + if (pb.getBuilding() == bld) + return &pb; + } + return NULL; +} + +bool Planner::isPlannableBuilding(BuildingTypeKey key) +{ + return item_for_building_type.count(std::get<0>(key)) > 0; } -bool Planner::allocatePlannedBuilding(df::building_type type) +bool Planner::allocatePlannedBuilding(BuildingTypeKey key) { coord32_t cursor; if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) return false; + auto type = std::get<0>(key); auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); if (!newinst) return false; @@ -430,53 +558,48 @@ bool Planner::allocatePlannedBuilding(df::building_type type) return true; } -PlannedBuilding *Planner::getSelectedPlannedBuilding() +Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) { - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) - { - if (building_iter->isCurrentlySelectedBuilding()) - { - return &(*building_iter); - } - } + static std::vector empty_vector; + static const ItemFiltersWrapper empty_ret(empty_vector); - return nullptr; + size_t nfilters = getNumFilters(key); + if (nfilters < 1) + return empty_ret; + while (default_item_filters[key].size() < nfilters) + default_item_filters[key].push_back(ItemFilter()); + return ItemFiltersWrapper(default_item_filters[key]); } -void Planner::removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); } - -ItemFilter *Planner::getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; } - -void Planner::adjustMinQuality(df::building_type type, int amount) +void Planner::doCycle() { - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - *min_quality = static_cast(*min_quality + amount); - - boundsCheckItemQuality(min_quality); - auto max_quality = &getDefaultItemFilterForType(type)->max_quality; - if (*min_quality > *max_quality) - (*max_quality) = *min_quality; + debug("Running Cycle"); + if (planned_buildings.size() == 0) + return; -} + debug("Planned count: %zu", planned_buildings.size()); -void Planner::adjustMaxQuality(df::building_type type, int amount) -{ - auto max_quality = &getDefaultItemFilterForType(type)->max_quality; - *max_quality = static_cast(*max_quality + amount); - - boundsCheckItemQuality(max_quality); - auto min_quality = &getDefaultItemFilterForType(type)->min_quality; - if (*max_quality < *min_quality) - (*min_quality) = *max_quality; -} + gather_available_items(); + for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + { + if (building_iter->isValid()) + { + auto type = building_iter->getBuilding()->getType(); + debug("Trying to allocate %s", enum_item_key_str(type)); -void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) -{ - *quality = static_cast(*quality); - if (*quality > item_quality::Artifact) - (*quality) = item_quality::Artifact; - if (*quality < item_quality::Ordinary) - (*quality) = item_quality::Ordinary; + auto required_item_type = item_for_building_type[type]; + auto items_vector = &available_item_vectors[required_item_type]; + if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) + { + debug("Unable to allocate an item"); + ++building_iter; + continue; + } + } + debug("Removing building plan"); + building_iter->remove(); + building_iter = planned_buildings.erase(building_iter); + } } void Planner::gather_available_items() diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index 67eeb9bff..9341ad03f 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -1,99 +1,131 @@ #pragma once -#include "df/item_quality.h" +#include + +#include "df/building.h" #include "df/dfhack_material_category.h" +#include "df/item_quality.h" +#include "df/job_item.h" + #include "modules/Materials.h" #include "modules/Persistence.h" -struct ItemFilter +class ItemFilter { +public: + ItemFilter(); + + void clear(); + bool deserialize(DFHack::PersistentDataItem &config); + void serialize(DFHack::PersistentDataItem &config) const; + + void addMaterialMask(uint32_t mask); + void clearMaterialMask(); + void setMaterials(std::vector materials); + + void incMinQuality(); + void decMinQuality(); + void incMaxQuality(); + void decMaxQuality(); + void toggleDecoratedOnly(); + + uint32_t getMaterialMask() const; + std::vector getMaterials() const; + std::string getMinQuality() const; + std::string getMaxQuality() const; + bool getDecoratedOnly() const; + + bool matches(df::dfhack_material_category mask) const; + bool matches(DFHack::MaterialInfo &material) const; + bool matches(df::item *item) const; + +private: df::dfhack_material_category mat_mask; std::vector materials; df::item_quality min_quality; df::item_quality max_quality; bool decorated_only; - ItemFilter(); - - bool matchesMask(DFHack::MaterialInfo &mat); - bool matches(const df::dfhack_material_category mask) const; - bool matches(DFHack::MaterialInfo &material) const; - bool matches(df::item *item); - - std::vector getMaterialFilterAsVector(); - std::string getMaterialFilterAsSerial(); - bool parseSerializedMaterialTokens(std::string str); - - std::string getMinQuality(); - std::string getMaxQuality(); - - bool isValid(); - void clear(); - -private: - bool valid; + bool deserializeMaterialMask(std::string ser); + bool deserializeMaterials(std::string ser); + void setMinQuality(int quality); + void setMaxQuality(int quality); + bool matchesMask(DFHack::MaterialInfo &mat) const; }; class PlannedBuilding { public: - PlannedBuilding(df::building *building, ItemFilter *filter); - PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); + PlannedBuilding(df::building *building, const std::vector &filters); + PlannedBuilding(DFHack::PersistentDataItem &config); bool assignClosestItem(std::vector *items_vector); bool assignItem(df::item *item); - bool isValid(); + bool isValid() const; void remove(); - df::building_type getType(); - bool isCurrentlySelectedBuilding(); - - ItemFilter *getFilter(); + df::building * getBuilding(); + const std::vector & getFilters() const; private: - df::building *building; DFHack::PersistentDataItem config; - df::coord pos; - ItemFilter filter; + df::building *building; + df::building::key_field_type building_id; + std::vector filters; +}; + +// building type, subtype, custom +typedef std::tuple BuildingTypeKey; + +BuildingTypeKey toBuildingTypeKey( + df::building_type btype, int16_t subtype, int32_t custom); +BuildingTypeKey toBuildingTypeKey(df::building *bld); +BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs); + +struct BuildingTypeKeyHash +{ + std::size_t operator() (const BuildingTypeKey & key) const; }; class Planner { public: - Planner(); - - bool isPlannableBuilding(const df::building_type type) const; - - void reset(DFHack::color_ostream &out); + class ItemFiltersWrapper + { + public: + ItemFiltersWrapper(std::vector & item_filters) + : item_filters(item_filters) { } + std::vector::reverse_iterator rbegin() const { return item_filters.rbegin(); } + std::vector::reverse_iterator rend() const { return item_filters.rend(); } + const std::vector & get() const { return item_filters; } + private: + std::vector &item_filters; + }; void initialize(); + void reset(); + bool allocatePlannedBuilding(BuildingTypeKey key); void addPlannedBuilding(df::building *bld); + PlannedBuilding *getPlannedBuilding(df::building *bld); - void doCycle(); - - bool allocatePlannedBuilding(df::building_type type); - - PlannedBuilding *getSelectedPlannedBuilding(); + bool isPlannableBuilding(BuildingTypeKey key); - void removeSelectedPlannedBuilding(); + // returns an empty vector if the type is not supported + ItemFiltersWrapper getItemFilters(BuildingTypeKey key); - ItemFilter *getDefaultItemFilterForType(df::building_type type); - - void adjustMinQuality(df::building_type type, int amount); - void adjustMaxQuality(df::building_type type, int amount); + void doCycle(); private: std::map item_for_building_type; - std::map default_item_filters; + std::unordered_map, + BuildingTypeKeyHash> default_item_filters; std::map> available_item_vectors; std::map is_relevant_item_type; //Needed for fast check when looping over all items - std::vector planned_buildings; - void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality); - void gather_available_items(); }; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 93d5ff4e4..1d25da6f0 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,5 +1,5 @@ -#include "buildingplan-lib.h" - +#include + #include "df/entity_position.h" #include "df/interface_key.h" #include "df/ui_build_selector.h" @@ -14,6 +14,7 @@ #include "uicommon.h" #include "listcolumn.h" +#include "buildingplan-lib.h" DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.15 @@ -24,18 +25,15 @@ REQUIRE_GLOBAL(world); #define MAX_MASK 10 #define MAX_MATERIAL 21 -using namespace DFHack; -using namespace df::enums; - bool show_help = false; bool quickfort_mode = false; bool in_dummy_screen = false; -std::map planmode_enabled; +std::unordered_map planmode_enabled; class ViewscreenChooseMaterial : public dfhack_viewscreen { public: - ViewscreenChooseMaterial(ItemFilter *filter); + ViewscreenChooseMaterial(ItemFilter &filter); void feed(set *input); @@ -47,14 +45,12 @@ private: ListColumn masks_column; ListColumn materials_column; int selected_column; - ItemFilter *filter; - - df::building_type btype; + ItemFilter &filter; void addMaskEntry(df::dfhack_material_category &mask, const std::string &text) { auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); - if (filter->matches(mask)) + if (filter.matches(mask)) entry.selected = true; masks_column.add(entry); @@ -131,7 +127,7 @@ private: material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); auto name = material.toString(); ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) + if (filter.matches(material)) entry.selected = true; materials_column.add(entry); @@ -147,7 +143,7 @@ private: if (!selected_category.whole || material.matches(selected_category)) { ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) + if (filter.matches(material)) entry.selected = true; materials_column.add(entry); @@ -167,9 +163,10 @@ private: } }; -DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } +const DFHack::MaterialInfo &material_info_identity_fn(const DFHack::MaterialInfo &m) { return m; } -ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) +ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter &filter) + : filter(filter) { selected_column = 0; masks_column.setTitle("Type"); @@ -179,7 +176,6 @@ ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) materials_column.left_margin = MAX_MASK + 3; materials_column.setTitle("Material"); materials_column.multiselect = true; - this->filter = filter; masks_column.changeHighlight(0); @@ -217,7 +213,7 @@ void ViewscreenChooseMaterial::feed(set *input) } if (input->count(interface_key::CUSTOM_SHIFT_C)) { - filter->clear(); + filter.clear(); masks_column.clearSelection(); materials_column.clearSelection(); populateMaterials(); @@ -225,17 +221,18 @@ void ViewscreenChooseMaterial::feed(set *input) else if (input->count(interface_key::SEC_SELECT)) { // Convert list selections to material filters - filter->mat_mask.whole = 0; - filter->materials.clear(); + filter.clearMaterialMask(); // Category masks auto masks = masks_column.getSelectedElems(); for (auto it = masks.begin(); it != masks.end(); ++it) - filter->mat_mask.whole |= it->whole; + filter.addMaterialMask(it->whole); // Specific materials auto materials = materials_column.getSelectedElems(); - transform_(materials, filter->materials, material_info_identity_fn); + std::vector materialInfos; + transform_(materials, materialInfos, material_info_identity_fn); + filter.setMaterials(materialInfos); Screen::dismiss(this); } @@ -285,179 +282,167 @@ void ViewscreenChooseMaterial::render() } //START Viewscreen Hook -static bool is_planmode_enabled(df::building_type type) +static bool is_planmode_enabled(BuildingTypeKey key) { - return planmode_enabled[type] || quickfort_mode; + return planmode_enabled[key] || quickfort_mode; } -struct buildingplan_hook : public df::viewscreen_dwarfmodest +struct buildingplan_query_hook : public df::viewscreen_dwarfmodest { - //START UI Methods typedef df::viewscreen_dwarfmodest interpose_base; - void send_key(const df::interface_key &key) - { - set< df::interface_key > keys; - keys.insert(key); - this->feed(&keys); - } - bool isInPlannedBuildingQueryMode() { return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding || ui->main.mode == df::ui_sidebar_mode::BuildingItems) && - planner.getSelectedPlannedBuilding(); + planner.getPlannedBuilding(world->selected_building); } - bool isInPlannedBuildingPlacementMode() + bool handleInput(set *input) { - return ui->main.mode == ui_sidebar_mode::Build && - df::global::ui_build_selector && - df::global::ui_build_selector->stage < 2 && - planner.isPlannableBuilding(ui_build_selector->building_type); - } + if (!isInPlannedBuildingQueryMode()) + return false; - std::vector getNoblePositionOfSelectedBuildingOwner() - { - std::vector np; - if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding || - !world->selected_building || - !world->selected_building->owner) + if (input->count(interface_key::SUSPENDBUILDING)) + return true; // Don't unsuspend planned buildings + if (input->count(interface_key::DESTROYBUILDING)) { - return np; + // remove persistent data and allow the parent to handle the key + // so the building is removed + planner.getPlannedBuilding(world->selected_building)->remove(); } - switch (world->selected_building->getType()) - { - case building_type::Bed: - case building_type::Chair: - case building_type::Table: - break; - default: - return np; - } + return false; + } - return getUniqueNoblePositions(world->selected_building->owner); + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); } - bool isInNobleRoomQueryMode() + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { - if (getNoblePositionOfSelectedBuildingOwner().size() > 0) - return canReserveRoom(world->selected_building); - else - return false; + INTERPOSE_NEXT(render)(); + + if (!isInPlannedBuildingQueryMode()) + return; + + // Hide suspend toggle option + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 20; + Screen::Pen pen(' ', COLOR_BLACK); + Screen::fillRect(pen, x, y, dims.menu_x2, y); + + // all current buildings only have one filter + auto & filter = planner.getPlannedBuilding(world->selected_building)->getFilters()[0]; + y = 24; + OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin); + ++y; + OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin); + OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin); + + if (filter.getDecoratedOnly()) + OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); + + OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); + auto filters = filter.getMaterials(); + for (auto it = filters.begin(); it != filters.end(); ++it) + OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); + } +}; + +struct buildingplan_place_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + bool isInPlannedBuildingPlacementMode() + { + return ui->main.mode == ui_sidebar_mode::Build && + df::global::ui_build_selector && + df::global::ui_build_selector->stage < 2 && + planner.isPlannableBuilding(toBuildingTypeKey(ui_build_selector)); } bool handleInput(set *input) { - if (isInPlannedBuildingPlacementMode()) + if (!isInPlannedBuildingPlacementMode()) { - auto type = ui_build_selector->building_type; - if (input->count(interface_key::CUSTOM_SHIFT_P)) - { - planmode_enabled[type] = !planmode_enabled[type]; - if (!is_planmode_enabled(type)) - { - Gui::refreshSidebar(); - in_dummy_screen = false; - } - return true; - } - else if (input->count(interface_key::CUSTOM_P) || - input->count(interface_key::CUSTOM_F) || - input->count(interface_key::CUSTOM_D) || - input->count(interface_key::CUSTOM_N)) - { - show_help = true; - } + show_help = false; + return false; + } - if (is_planmode_enabled(type)) + if (in_dummy_screen) + { + if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) + || input->count(interface_key::LEAVESCREEN)) { - if (quickfort_mode && in_dummy_screen) - { - if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) - || input->count(interface_key::LEAVESCREEN)) - { - in_dummy_screen = false; - send_key(interface_key::LEAVESCREEN); - } - - return true; - } - - if (input->count(interface_key::SELECT)) - { - if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) - { - Gui::refreshSidebar(); - if (quickfort_mode) - { - in_dummy_screen = true; - } - } - - return true; - } - else if (input->count(interface_key::CUSTOM_SHIFT_F)) - { - quickfort_mode = !quickfort_mode; - } - else if (input->count(interface_key::CUSTOM_SHIFT_M)) - { - Screen::show(dts::make_unique(planner.getDefaultItemFilterForType(type)), plugin_self); - } - else if (input->count(interface_key::CUSTOM_Q)) - { - planner.adjustMinQuality(type, -1); - } - else if (input->count(interface_key::CUSTOM_W)) - { - planner.adjustMinQuality(type, 1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_Q)) - { - planner.adjustMaxQuality(type, -1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_W)) - { - planner.adjustMaxQuality(type, 1); - } - else if (input->count(interface_key::CUSTOM_SHIFT_D)) - { - planner.getDefaultItemFilterForType(type)->decorated_only = - !planner.getDefaultItemFilterForType(type)->decorated_only; - } + in_dummy_screen = false; + // pass LEAVESCREEN up to parent view + input->clear(); + input->insert(interface_key::LEAVESCREEN); + return false; } + return true; } - else if (isInPlannedBuildingQueryMode()) + + if (input->count(interface_key::CUSTOM_P) || + input->count(interface_key::CUSTOM_F) || + input->count(interface_key::CUSTOM_D) || + input->count(interface_key::CUSTOM_M)) { - if (input->count(interface_key::SUSPENDBUILDING)) - { - return true; // Don't unsuspend planned buildings - } - else if (input->count(interface_key::DESTROYBUILDING)) - { - planner.removeSelectedPlannedBuilding(); // Remove persistent data - } + show_help = true; + } + BuildingTypeKey key = toBuildingTypeKey(ui_build_selector); + if (input->count(interface_key::CUSTOM_SHIFT_P)) + { + planmode_enabled[key] = !planmode_enabled[key]; + if (!is_planmode_enabled(key)) + Gui::refreshSidebar(); + return true; } - else if (isInNobleRoomQueryMode()) + if (input->count(interface_key::CUSTOM_SHIFT_F)) { - if (Gui::inRenameBuilding()) - return false; - auto np = getNoblePositionOfSelectedBuildingOwner(); - df::interface_key last_token = get_string_key(input); - if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058) + quickfort_mode = !quickfort_mode; + return true; + } + + if (!is_planmode_enabled(key)) + return false; + + if (input->count(interface_key::SELECT)) + { + if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(key)) { - size_t selection = last_token - interface_key::STRING_A048; - if (np.size() < selection) - return false; - roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); - return true; + Gui::refreshSidebar(); + if (quickfort_mode) + in_dummy_screen = true; } + return true; } - return false; + // all current buildings only have one filter + auto filter = planner.getItemFilters(key).rbegin(); + if (input->count(interface_key::CUSTOM_SHIFT_M)) + Screen::show(dts::make_unique(*filter), plugin_self); + else if (input->count(interface_key::CUSTOM_Q)) + filter->decMinQuality(); + else if (input->count(interface_key::CUSTOM_W)) + filter->incMinQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_Q)) + filter->decMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_W)) + filter->incMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_D)) + filter->toggleDecoratedOnly(); + else + return false; + return true; } DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) @@ -469,133 +454,176 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest DEFINE_VMETHOD_INTERPOSE(void, render, ()) { bool plannable = isInPlannedBuildingPlacementMode(); - if (plannable && is_planmode_enabled(ui_build_selector->building_type)) + BuildingTypeKey key = toBuildingTypeKey(ui_build_selector); + if (plannable && is_planmode_enabled(key)) { if (ui_build_selector->stage < 1) - { // No materials but turn on cursor ui_build_selector->stage = 1; - } - for (auto iter = ui_build_selector->errors.begin(); iter != ui_build_selector->errors.end();) + for (auto iter = ui_build_selector->errors.begin(); + iter != ui_build_selector->errors.end();) { - //FIXME Hide bags - if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") || - (*iter)->find("No access") != string::npos) - { + // FIXME Hide bags + if (((*iter)->find("Needs") != string::npos + && **iter != "Needs adjacent wall") + || (*iter)->find("No access") != string::npos) iter = ui_build_selector->errors.erase(iter); - } else - { ++iter; - } } } INTERPOSE_NEXT(render)(); + if (!plannable) + return; + auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; - auto type = ui_build_selector->building_type; - if (plannable) + + if (in_dummy_screen) { - if (quickfort_mode && in_dummy_screen) - { - Screen::Pen pen(' ',COLOR_BLACK); - int y = dims.y1 + 1; - Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); + Screen::Pen pen(' ',COLOR_BLACK); + int y = dims.y1 + 1; + Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); - ++y; + ++y; - OutputString(COLOR_BROWN, x, y, "Quickfort Placeholder", true, left_margin); - OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); - } - else - { - int y = 23; + OutputString(COLOR_BROWN, x, y, + "Placeholder for legacy Quickfort. This screen is not required for DFHack native quickfort.", + true, left_margin); + OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); + return; + } - if (show_help) - { - OutputString(COLOR_BROWN, x, y, "Note: "); - OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); - } - OutputToggleString(x, y, "Planning Mode", "P", is_planmode_enabled(type), true, left_margin); + int y = 23; - if (is_planmode_enabled(type)) - { - OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); + if (show_help) + { + OutputString(COLOR_BROWN, x, y, "Note: "); + OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); + } - auto filter = planner.getDefaultItemFilterForType(type); + OutputToggleString(x, y, "Planning Mode", "P", planmode_enabled[key], true, left_margin); + OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); - OutputHotkeyString(x, y, "Min Quality: ", "qw"); - OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); + if (!is_planmode_enabled(key)) + return; - OutputHotkeyString(x, y, "Max Quality: ", "QW"); - OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); + auto filter = planner.getItemFilters(key).rbegin(); + y += 2; + OutputHotkeyString(x, y, "Min Quality: ", "qw"); + OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); - OutputToggleString(x, y, "Decorated Only: ", "D", filter->decorated_only, true, left_margin); + OutputHotkeyString(x, y, "Max Quality: ", "QW"); + OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); - OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); - auto filter_descriptions = filter->getMaterialFilterAsVector(); - for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it) - OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); - } - else - { - in_dummy_screen = false; - } - } - } - else if (isInPlannedBuildingQueryMode()) + OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin); + + OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); + auto filter_descriptions = filter->getMaterials(); + for (auto it = filter_descriptions.begin(); + it != filter_descriptions.end(); ++it) + OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); + } +}; + +struct buildingplan_room_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + std::vector getNoblePositionOfSelectedBuildingOwner() + { + std::vector np; + if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding || + !world->selected_building || + !world->selected_building->owner) { - in_dummy_screen = false; - - // Hide suspend toggle option - int y = 20; - Screen::Pen pen(' ', COLOR_BLACK); - Screen::fillRect(pen, x, y, dims.menu_x2, y); - - auto filter = planner.getSelectedPlannedBuilding()->getFilter(); - y = 24; - OutputString(COLOR_BROWN, x, y, "Planned Building Filter:", true, left_margin); - OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter->getMinQuality(), true, left_margin); - OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter->getMaxQuality(), true, left_margin); - - if (filter->decorated_only) - OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); - - OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); - auto filters = filter->getMaterialFilterAsVector(); - for (auto it = filters.begin(); it != filters.end(); ++it) - OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); + return np; } - else if (isInNobleRoomQueryMode()) + + switch (world->selected_building->getType()) { - auto np = getNoblePositionOfSelectedBuildingOwner(); - int y = 24; - OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); - OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); - for (size_t i = 0; i < np.size() && i < 9; i++) - { - bool enabled = (roomMonitor.getReservedNobleCode(world->selected_building->id) - == np[i].position->code); - OutputToggleString(x, y, np[i].position->name[0].c_str(), - int_to_string(i+1).c_str(), enabled, true, left_margin); - } + case building_type::Bed: + case building_type::Chair: + case building_type::Table: + break; + default: + return np; } + + return getUniqueNoblePositions(world->selected_building->owner); + } + + bool isInNobleRoomQueryMode() + { + if (getNoblePositionOfSelectedBuildingOwner().size() > 0) + return canReserveRoom(world->selected_building); else + return false; + } + + bool handleInput(set *input) + { + if (!isInNobleRoomQueryMode()) + return false; + + if (Gui::inRenameBuilding()) + return false; + auto np = getNoblePositionOfSelectedBuildingOwner(); + df::interface_key last_token = get_string_key(input); + if (last_token >= interface_key::STRING_A048 + && last_token <= interface_key::STRING_A058) { - in_dummy_screen = false; - show_help = false; + size_t selection = last_token - interface_key::STRING_A048; + if (np.size() < selection) + return false; + roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code); + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (!isInNobleRoomQueryMode()) + return; + + auto np = getNoblePositionOfSelectedBuildingOwner(); + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 24; + OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); + OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); + for (size_t i = 0; i < np.size() && i < 9; i++) + { + bool enabled = + roomMonitor.getReservedNobleCode(world->selected_building->id) + == np[i].position->code; + OutputToggleString(x, y, np[i].position->name[0].c_str(), + int_to_string(i+1).c_str(), enabled, true, left_margin); } } }; -IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render); static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { @@ -624,10 +652,14 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { - planner.reset(out); - - if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) || - !INTERPOSE_HOOK(buildingplan_hook, render).apply(enable)) + planner.reset(); + + if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_room_hook, feed).apply(enable) || + !INTERPOSE_HOOK(buildingplan_query_hook, render).apply(enable) || + !INTERPOSE_HOOK(buildingplan_place_hook, render).apply(enable) || + !INTERPOSE_HOOK(buildingplan_room_hook, render).apply(enable)) return CR_FAILURE; is_enabled = enable; @@ -640,7 +672,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector frame_counter % (DAY_TICKS/2) == 0) + if (Maps::IsValid() && !World::ReadPauseState() + && (cycle_requested || world->frame_counter % (DAY_TICKS/2) == 0)) { planner.doCycle(); roomMonitor.doCycle(); + cycle_requested = false; } return CR_OK; @@ -680,8 +716,11 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) // Lua API section -static bool isPlannableBuilding(df::building_type type) { - return planner.isPlannableBuilding(type); +static bool isPlannableBuilding(df::building_type type, + int16_t subtype, + int32_t custom) { + return planner.isPlannableBuilding( + toBuildingTypeKey(type, subtype, custom)); } static void addPlannedBuilding(df::building *bld) { @@ -692,9 +731,14 @@ static void doCycle() { planner.doCycle(); } +static void scheduleCycle() { + cycle_requested = true; +} + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isPlannableBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding), DFHACK_LUA_FUNCTION(doCycle), + DFHACK_LUA_FUNCTION(scheduleCycle), DFHACK_LUA_END }; diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 72d11b78a..ff8aa547f 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -48,7 +48,7 @@ struct BuildingInfo { } bool allocate() { - return planner.allocatePlannedBuilding(type); + return planner.allocatePlannedBuilding(toBuildingTypeKey(type, -1, -1)); } }; @@ -122,7 +122,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { - planner.reset(out); + planner.reset(); is_enabled = enable; } diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 071c07395..157b6211b 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -4,9 +4,10 @@ local _ENV = mkmodule('plugins.buildingplan') Native functions: - * bool isPlannableBuilding(df::building_type type) + * bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) * void addPlannedBuilding(df::building *bld) * void doCycle() + * void scheduleCycle() --]] diff --git a/plugins/uicommon.h b/plugins/uicommon.h index d8624786c..78256ac69 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -79,7 +79,7 @@ static void for_each_(map &v, Fn func) } template -static void transform_(vector &src, vector &dst, Fn func) +static void transform_(const vector &src, vector &dst, Fn func) { transform(src.begin(), src.end(), back_inserter(dst), func); } From 1368fb400338a92b3b5970917fee6cb0ca067aa2 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 16 Oct 2020 14:03:05 -0700 Subject: [PATCH 016/112] buildingplan: construct buildings from lua Replace C++ building construction code with lua constructBuilding so we can get the proper job_item filters set. these filters will be used when we replace the core buildingplan algorithm in the next PR. --- plugins/CMakeLists.txt | 2 +- plugins/buildingplan-planner.cpp | 36 ------------------------- plugins/buildingplan-planner.h | 1 - plugins/buildingplan.cpp | 29 +++++++++++++++++++- plugins/fortplan.cpp | 45 +++++++++++++++++++++++++++----- plugins/lua/buildingplan.lua | 36 +++++++++++++++++++++++++ plugins/lua/fortplan.lua | 13 +++++++++ 7 files changed, 117 insertions(+), 45 deletions(-) create mode 100644 plugins/lua/fortplan.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 116bdd445..46a7d4097 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -126,7 +126,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(flows flows.cpp) dfhack_plugin(follow follow.cpp) dfhack_plugin(forceequip forceequip.cpp) - dfhack_plugin(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib) + dfhack_plugin(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib lua) dfhack_plugin(generated-creature-renamer generated-creature-renamer.cpp) dfhack_plugin(getplants getplants.cpp) dfhack_plugin(hotkeys hotkeys.cpp) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 3cb27145e..925d0f4c3 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -522,42 +522,6 @@ bool Planner::isPlannableBuilding(BuildingTypeKey key) return item_for_building_type.count(std::get<0>(key)) > 0; } -bool Planner::allocatePlannedBuilding(BuildingTypeKey key) -{ - coord32_t cursor; - if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) - return false; - - auto type = std::get<0>(key); - auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); - if (!newinst) - return false; - - df::job_item *filter = new df::job_item(); - filter->item_type = item_type::NONE; - filter->mat_index = 0; - filter->flags2.bits.building_material = true; - std::vector filters; - filters.push_back(filter); - - if (!Buildings::constructWithFilters(newinst, filters)) - { - delete newinst; - return false; - } - - if (type == building_type::Door) - { - auto door = virtual_cast(newinst); - if (door) - door->door_flags.bits.pet_passable = true; - } - - addPlannedBuilding(newinst); - - return true; -} - Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) { static std::vector empty_vector; diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index 9341ad03f..cda217d81 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -106,7 +106,6 @@ public: void initialize(); void reset(); - bool allocatePlannedBuilding(BuildingTypeKey key); void addPlannedBuilding(df::building *bld); PlannedBuilding *getPlannedBuilding(df::building *bld); diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 1d25da6f0..703e567b7 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -287,6 +287,33 @@ static bool is_planmode_enabled(BuildingTypeKey key) return planmode_enabled[key] || quickfort_mode; } +static bool construct_planned_building() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + if (!(lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.buildingplan", + "construct_building_from_ui_state") && + Lua::SafeCall(out, L, 0, 1))) + { + return false; + } + + auto bld = static_cast(LuaWrapper::get_object_ref(L, -1)); + lua_pop(L, 1); + + if (!bld) + return false; + + planner.addPlannedBuilding(bld); + + return true; +} + struct buildingplan_query_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -417,7 +444,7 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest if (input->count(interface_key::SELECT)) { - if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(key)) + if (ui_build_selector->errors.size() == 0 && construct_planned_building()) { Gui::refreshSidebar(); if (quickfort_mode) diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index ff8aa547f..8fbf8fb72 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -1,14 +1,17 @@ #include #include -#include "df/world.h" +#include "df/job_item.h" #include "df/trap_type.h" +#include "df/world.h" +#include "modules/Buildings.h" #include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/Maps.h" #include "modules/World.h" +#include "LuaTools.h" #include "PluginManager.h" #include "buildingplan-lib.h" @@ -47,8 +50,38 @@ struct BuildingInfo { hasCustomOptions = false; } - bool allocate() { - return planner.allocatePlannedBuilding(toBuildingTypeKey(type, -1, -1)); + bool allocate(coord32_t cursor) { + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic(out, L, "plugins.fortplan", + "construct_building_from_params")) + { + return false; + } + + Lua::Push(L, type); + Lua::Push(L, cursor.x); + Lua::Push(L, cursor.y); + Lua::Push(L, cursor.z); + + if (!Lua::SafeCall(out, L, 4, 1)) + return false; + + auto bld = + static_cast(LuaWrapper::get_object_ref(L, -1)); + lua_pop(L, 1); + + if (!bld) + return false; + + planner.addPlannedBuilding(bld); + + return true; } }; @@ -347,13 +380,13 @@ command_result fortplan(color_ostream &out, vector & params) { offsetCursor.x -= xOffset; offsetCursor.y -= yOffset; DFHack::Gui::setCursorCoords(offsetCursor.x, offsetCursor.y, offsetCursor.z); - if (!buildingInfo.allocate()) { + if (!buildingInfo.allocate(offsetCursor)) { con.print("*** There was an error placing building with code '%s' centered at (%zu,%zu).\n",curCode.c_str(),x,y); } DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); } else if (block) { //con.print("Placing a building with code '%s' with corner at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); - if (!buildingInfo.allocate()) { + if (!buildingInfo.allocate(cursor)) { con.print("*** There was an error placing building with code '%s' with corner at (%zu,%zu).\n",curCode.c_str(),x,y); } } else { @@ -366,7 +399,7 @@ command_result fortplan(color_ostream &out, vector & params) { } } else { //con.print("Building a(n) %s.\n",buildingInfo.name.c_str()); - if (!buildingInfo.allocate()) { + if (!buildingInfo.allocate(cursor)) { con.print("*** There was an error placing the %s at (%zu,%zu).\n",buildingInfo.name.c_str(),x,y); } } diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 157b6211b..1ab462201 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -11,4 +11,40 @@ local _ENV = mkmodule('plugins.buildingplan') --]] +local guidm = require('gui.dwarfmode') +require('dfhack.buildings') + +-- needs the core suspended +function construct_building_from_ui_state() + local uibs = df.global.ui_build_selector + local world = df.global.world + local direction = world.selected_direction + local _, width, height = dfhack.buildings.getCorrectSize( + world.building_width, world.building_height, uibs.building_type, + uibs.building_subtype, uibs.custom_type, direction) + -- the cursor is at the center of the building; we need the upper-left + -- corner of the building + local pos = guidm.getCursorPos() + pos.x = pos.x - math.floor(width/2) + pos.y = pos.y - math.floor(height/2) + local bld, err = dfhack.buildings.constructBuilding{ + type=uibs.building_type, subtype=uibs.building_subtype, + custom=uibs.custom_type, pos=pos, width=width, height=height, + direction=direction} + if err then error(err) end + -- TODO: assign fields for the types that need them. we can't pass them all + -- in to the call to constructBuilding since the unneeded fields will cause + -- errors + --local fields = { + -- friction=uibs.friction, + -- use_dump=uibs.use_dump, + -- dump_x_shift=uibs.dump_x_shift, + -- dump_y_shift=uibs.dump_y_shift, + -- speed=uibs.speed + --} + -- TODO: use quickfort's post_construction_fns? maybe move those functions + -- into the library so they get applied automatically + return bld +end + return _ENV diff --git a/plugins/lua/fortplan.lua b/plugins/lua/fortplan.lua new file mode 100644 index 000000000..71e69c69d --- /dev/null +++ b/plugins/lua/fortplan.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.fortplan') + +require('dfhack.buildings') + +function construct_building_from_params(building_type, x, y, z) + local pos = xyz2pos(x, y, z) + local bld, err = + dfhack.buildings.constructBuilding{type=building_type, pos=pos} + if err then error(err) end + return bld +end + +return _ENV From 100b374af73659b2f00a9c26697f3b17fd1a3a72 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 16 Oct 2020 14:08:52 -0700 Subject: [PATCH 017/112] generalize buildingplan for all building types but restrict to only the currently supported set so we can still assume only one filter is required for each building. changes: - update buildingplan plugin version to 2.0 - new serialization format for planned buildings - old persistent data is automatically migrated to new format on load - algorithm now respects job_item filters; items must match job_item filter and buildingplan ItemFilter - more invalid items are now filtered out, like items encased in ice. are there any others we should be checking (see BadFlags struct) - items are sorted before job is unsuspended so final item ordering is correct regardless of what order the items were matched and attached - item counts in filters are kept up to date so if buildingplan is disabled before all filters are matched and the building is completed by DF itself, the item counts will come out correct (though the item ordering and building "roughness" may be incorrect) - fixes two memory leaks in building finalization code - allows artifacts to be matched (ItemFilter defaults now top out at Masterful -- Artifact is selectable but must be manually specified) - add gui to switch between items for buildings that require multiple item types --- plugins/buildingplan-planner.cpp | 714 ++++++++++++++++++++++--------- plugins/buildingplan-planner.h | 30 +- plugins/buildingplan.cpp | 163 ++++++- plugins/fortplan.cpp | 2 - plugins/lua/buildingplan.lua | 47 ++ 5 files changed, 719 insertions(+), 237 deletions(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 925d0f4c3..dcdcfb777 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -12,12 +12,14 @@ #include "modules/Gui.h" #include "modules/Job.h" +#include "LuaTools.h" #include "uicommon.h" #include "buildingplan-planner.h" #include "buildingplan-lib.h" static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints"; +static const std::string planned_building_persistence_key_v2 = "buildingplan/constraints2"; /* * ItemFilter @@ -37,24 +39,24 @@ void ItemFilter::clear() materials.clear(); } -bool ItemFilter::deserialize(PersistentDataItem &config) +bool ItemFilter::deserialize(std::string ser) { clear(); std::vector tokens; - split_string(&tokens, config.val(), "/"); - if (tokens.size() != 2) + split_string(&tokens, ser, "/"); + if (tokens.size() != 5) { - debug("invalid ItemFilter serialization: '%s'", config.val().c_str()); + debug("invalid ItemFilter serialization: '%s'", ser.c_str()); return false; } if (!deserializeMaterialMask(tokens[0]) || !deserializeMaterials(tokens[1])) return false; - setMinQuality(config.ival(2) - 1); - setMaxQuality(config.ival(4) - 1); - decorated_only = config.ival(3) - 1; + setMinQuality(atoi(tokens[2].c_str())); + setMaxQuality(atoi(tokens[3].c_str())); + decorated_only = static_cast(atoi(tokens[4].c_str())); return true; } @@ -91,7 +93,8 @@ bool ItemFilter::deserializeMaterials(std::string ser) return true; } -void ItemFilter::serialize(PersistentDataItem &config) const +// format: mat,mask,elements/materials,list/minq/maxq/decorated +std::string ItemFilter::serialize() const { std::ostringstream ser; ser << bitfield_to_string(mat_mask, ",") << "/"; @@ -101,10 +104,10 @@ void ItemFilter::serialize(PersistentDataItem &config) const for (size_t i = 1; i < materials.size(); ++i) ser << "," << materials[i].getToken(); } - config.val() = ser.str(); - config.ival(2) = min_quality + 1; - config.ival(4) = max_quality + 1; - config.ival(3) = static_cast(decorated_only) + 1; + ser << "/" << static_cast(min_quality); + ser << "/" << static_cast(max_quality); + ser << "/" << static_cast(decorated_only); + return ser.str(); } void ItemFilter::clearMaterialMask() @@ -230,20 +233,59 @@ bool ItemFilter::matches(df::item *item) const * PlannedBuilding */ -static std::vector deserializeFilters(PersistentDataItem &config) +// format: itemfilterser|itemfilterser|... +static std::string serializeFilters(const std::vector &filters) { - // simplified implementation while we can assume there is only one filter + std::ostringstream ser; + if (!filters.empty()) + { + ser << filters[0].serialize(); + for (size_t i = 1; i < filters.size(); ++i) + ser << "|" << filters[i].serialize(); + } + return ser.str(); +} + +static std::vector deserializeFilters(std::string ser) +{ + std::vector isers; + split_string(&isers, ser, "|"); std::vector ret; - ItemFilter itemFilter; - itemFilter.deserialize(config); - ret.push_back(itemFilter); + for (auto & iser : isers) + { + ItemFilter filter; + if (filter.deserialize(iser)) + ret.push_back(filter); + } return ret; } static size_t getNumFilters(BuildingTypeKey key) { - // TODO: get num filters in Lua when we handle all building types - return 1; + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 4) || !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "get_num_filters")) + { + debug("failed to push the lua method on the stack"); + return 0; + } + + Lua::Push(L, std::get<0>(key)); + Lua::Push(L, std::get<1>(key)); + Lua::Push(L, std::get<2>(key)); + + if (!Lua::SafeCall(out, L, 3, 1)) + { + debug("lua call failed"); + return 0; + } + + int num_filters = lua_tonumber(L, -1); + lua_pop(L, 1); + return num_filters; } PlannedBuilding::PlannedBuilding(df::building *building, const std::vector &filters) @@ -251,92 +293,27 @@ PlannedBuilding::PlannedBuilding(df::building *building, const std::vectorid), filters(filters) { - config = DFHack::World::AddPersistentData(planned_building_persistence_key_v1); - config.ival(1) = building_id; - // assume all filter vectors are length 1 for now - filters[0].serialize(config); + config = DFHack::World::AddPersistentData(planned_building_persistence_key_v2); + config.ival(0) = building_id; + config.val() = serializeFilters(filters); } PlannedBuilding::PlannedBuilding(PersistentDataItem &config) : config(config), - building(df::building::find(config.ival(1))), - building_id(config.ival(1)), - filters(deserializeFilters(config)) -{ } - -bool PlannedBuilding::assignClosestItem(std::vector *items_vector) + building(df::building::find(config.ival(0))), + building_id(config.ival(0)), + filters(deserializeFilters(config.val())) { - decltype(items_vector->begin()) closest_item; - int32_t closest_distance = -1; - for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) - { - auto item = *item_iter; - if (!filters[0].matches(item)) - continue; - - auto pos = item->pos; - auto distance = abs(pos.x - building->centerx) + - abs(pos.y - building->centery) + - abs(pos.z - building->z) * 50; - - if (closest_distance > -1 && distance >= closest_distance) - continue; - - closest_distance = distance; - closest_item = item_iter; - } - - if (closest_distance > -1 && assignItem(*closest_item)) - { - debug("Item assigned"); - items_vector->erase(closest_item); - remove(); - return true; - } - - return false; -} - -void delete_item_fn(df::job_item *x) { delete x; } - -bool PlannedBuilding::assignItem(df::item *item) -{ - auto ref = df::allocate(); - if (!ref) - { - Core::printerr("Could not allocate general_ref_building_holderst\n"); - return false; - } - - ref->building_id = building->id; - - if (building->jobs.size() != 1) - return false; - - auto job = building->jobs[0]; - - for_each_(job->job_items, delete_item_fn); - job->job_items.clear(); - job->flags.bits.suspend = false; - - bool rough = false; - Job::attachJobItem(job, item, df::job_item_ref::Hauled); - if (item->getType() == item_type::BOULDER) - rough = true; - building->mat_type = item->getMaterial(); - building->mat_index = item->getMaterialIndex(); - - job->mat_type = building->mat_type; - job->mat_index = building->mat_index; - - if (building->needsDesign()) + if (building) { - auto act = (df::building_actual *) building; - act->design = new df::building_design(); - act->design->flags.bits.rough = rough; + if (filters.size() != + getNumFilters(toBuildingTypeKey(building))) + { + debug("invalid ItemFilter vector serialization: '%s'", + config.val().c_str()); + building = NULL; + } } - - return true; } // Ensure the building still exists and is in a valid state. It can disappear @@ -361,6 +338,8 @@ df::building * PlannedBuilding::getBuilding() const std::vector & PlannedBuilding::getFilters() const { + // if we want to be able to dynamically change the filters, we'll need to + // re-bucket the tasks in Planner. return filters; } @@ -412,45 +391,112 @@ std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const * Planner */ -void Planner::initialize() -{ -#define add_building_type(btype, itype) \ - item_for_building_type[df::building_type::btype] = df::item_type::itype; \ - available_item_vectors[df::item_type::itype] = std::vector(); \ - is_relevant_item_type[df::item_type::itype] = true; \ - - FOR_ENUM_ITEMS(item_type, it) - is_relevant_item_type[it] = false; - - add_building_type(Armorstand, ARMORSTAND); - add_building_type(Bed, BED); - add_building_type(Chair, CHAIR); - add_building_type(Coffin, COFFIN); - add_building_type(Door, DOOR); - add_building_type(Floodgate, FLOODGATE); - add_building_type(Hatch, HATCH_COVER); - add_building_type(GrateWall, GRATE); - add_building_type(GrateFloor, GRATE); - add_building_type(BarsVertical, BAR); - add_building_type(BarsFloor, BAR); - add_building_type(Cabinet, CABINET); - add_building_type(Box, BOX); - // skip kennels, farm plot - add_building_type(Weaponrack, WEAPONRACK); - add_building_type(Statue, STATUE); - add_building_type(Slab, SLAB); - add_building_type(Table, TABLE); - // skip roads ... furnaces - add_building_type(WindowGlass, WINDOW); - // skip gem window ... support - add_building_type(AnimalTrap, ANIMALTRAP); - add_building_type(Chain, CHAIN); - add_building_type(Cage, CAGE); - // skip archery target - add_building_type(TractionBench, TRACTION_BENCH); - // skip nest box, hive (tools) - -#undef add_building_type +// convert v1 persistent data into v2 format +// we can remove this conversion code once v2 has been live for a while +void migrateV1ToV2() +{ + std::vector configs; + DFHack::World::GetPersistentData(&configs, planned_building_persistence_key_v1); + if (configs.empty()) + return; + + debug("migrating %zu persisted configs to new format", configs.size()); + for (auto config : configs) + { + df::building *bld = df::building::find(config.ival(1)); + if (!bld) + { + debug("buliding no longer exists; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + + if (bld->getBuildStage() != 0 || bld->jobs.size() != 1 + || bld->jobs[0]->job_items.size() != 1) + { + debug("building in invalid state; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + + // fix up the building so we can set the material properties later + bld->mat_type = -1; + bld->mat_index = -1; + + // the v1 filters are not initialized correctly and will match any item. + // we need to fix them up a bit. + auto filter = bld->jobs[0]->job_items[0]; + df::item_type type; + switch (bld->getType()) + { + case df::building_type::Armorstand: type = df::item_type::ARMORSTAND; break; + case df::building_type::Bed: type = df::item_type::BED; break; + case df::building_type::Chair: type = df::item_type::CHAIR; break; + case df::building_type::Coffin: type = df::item_type::COFFIN; break; + case df::building_type::Door: type = df::item_type::DOOR; break; + case df::building_type::Floodgate: type = df::item_type::FLOODGATE; break; + case df::building_type::Hatch: type = df::item_type::HATCH_COVER; break; + case df::building_type::GrateWall: type = df::item_type::GRATE; break; + case df::building_type::GrateFloor: type = df::item_type::GRATE; break; + case df::building_type::BarsVertical: type = df::item_type::BAR; break; + case df::building_type::BarsFloor: type = df::item_type::BAR; break; + case df::building_type::Cabinet: type = df::item_type::CABINET; break; + case df::building_type::Box: type = df::item_type::BOX; break; + case df::building_type::Weaponrack: type = df::item_type::WEAPONRACK; break; + case df::building_type::Statue: type = df::item_type::STATUE; break; + case df::building_type::Slab: type = df::item_type::SLAB; break; + case df::building_type::Table: type = df::item_type::TABLE; break; + case df::building_type::WindowGlass: type = df::item_type::WINDOW; break; + case df::building_type::AnimalTrap: type = df::item_type::ANIMALTRAP; break; + case df::building_type::Chain: type = df::item_type::CHAIN; break; + case df::building_type::Cage: type = df::item_type::CAGE; break; + case df::building_type::TractionBench: type = df::item_type::TRACTION_BENCH; break; + default: + debug("building has unhandled type; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + filter->item_type = type; + filter->item_subtype = -1; + filter->mat_type = -1; + filter->mat_index = -1; + filter->flags1.whole = 0; + filter->flags2.whole = 0; + filter->flags2.bits.allow_artifact = true; + filter->flags3.whole = 0; + filter->flags4 = 0; + filter->flags5 = 0; + filter->metal_ore = -1; + filter->min_dimension = -1; + filter->has_tool_use = df::tool_uses::NONE; + filter->quantity = 1; + + std::vector tokens; + split_string(&tokens, config.val(), "/"); + if (tokens.size() != 2) + { + debug("invalid v1 format; removing config"); + DFHack::World::DeletePersistentData(config); + continue; + } + + ItemFilter item_filter; + item_filter.deserializeMaterialMask(tokens[0]); + item_filter.deserializeMaterials(tokens[1]); + item_filter.setMinQuality(config.ival(2) - 1); + item_filter.setMaxQuality(config.ival(4) - 1); + if (config.ival(3) - 1) + item_filter.toggleDecoratedOnly(); + + // create the v2 record + std::vector item_filters; + item_filters.push_back(item_filter); + PlannedBuilding pb(bld, item_filters); + + // remove the v1 record + DFHack::World::DeletePersistentData(config); + debug("v1 record successfully migrated"); + } } void Planner::reset() @@ -458,9 +504,12 @@ void Planner::reset() debug("resetting Planner state"); default_item_filters.clear(); planned_buildings.clear(); + tasks.clear(); + + migrateV1ToV2(); std::vector items; - DFHack::World::GetPersistentData(&items, planned_building_persistence_key_v1); + DFHack::World::GetPersistentData(&items, planned_building_persistence_key_v2); debug("found data for %zu planned buildings", items.size()); for (auto i = items.begin(); i != items.end(); i++) @@ -472,7 +521,8 @@ void Planner::reset() continue; } - planned_buildings.push_back(pb); + if (registerTasks(pb)) + planned_buildings.insert(std::make_pair(pb.getBuilding()->id, pb)); } } @@ -487,19 +537,19 @@ void Planner::addPlannedBuilding(df::building *bld) } // protect against multiple registrations - if (getPlannedBuilding(bld)) + if (planned_buildings.count(bld->id) != 0) { - debug("building already registered"); + debug("failed to add building: already registered"); return; } PlannedBuilding pb(bld, item_filters); - if (pb.isValid()) + if (pb.isValid() && registerTasks(pb)) { for (auto job : bld->jobs) job->flags.bits.suspend = true; - planned_buildings.push_back(pb); + planned_buildings.insert(std::make_pair(bld->id, pb)); } else { @@ -507,19 +557,107 @@ void Planner::addPlannedBuilding(df::building *bld) } } -PlannedBuilding * Planner::getPlannedBuilding(df::building *bld) +static std::string getBucket(const df::job_item & ji, + const std::vector & item_filters) +{ + std::ostringstream ser; + + // pull out and serialize only known relevant fields. if we miss a few, then + // the filter bucket will be slighly less specific than it could be, but + // that's probably ok. we'll just end up bucketing slightly different items + // together. this is only a problem if the different filter at the front of + // the queue doesn't match any available items and blocks filters behind it + // that could be matched. + ser << ji.item_type << ':' << ji.item_subtype << ':' << ji.mat_type << ':' + << ji.mat_index << ':' << ji.flags1.whole << ':' << ji.flags2.whole + << ':' << ji.flags3.whole << ':' << ji.flags4 << ':' << ji.flags5 << ':' + << ji.metal_ore << ':' << ji.has_tool_use; + + for (auto & item_filter : item_filters) + { + ser << ':' << item_filter.serialize(); + } + + return ser.str(); +} + +bool Planner::registerTasks(PlannedBuilding & pb) { - for (auto & pb : planned_buildings) + df::building * bld = pb.getBuilding(); + if (bld->jobs.size() != 1) + { + debug("unexpected number of jobs: want 1, got %zu", bld->jobs.size()); + return false; + } + auto job_items = bld->jobs[0]->job_items; + int num_job_items = job_items.size(); + if (num_job_items < 1) { - if (pb.getBuilding() == bld) - return &pb; + debug("unexpected number of job items: want >0, got %d", num_job_items); + return false; } - return NULL; + for (int job_item_idx = 0; job_item_idx < num_job_items; ++job_item_idx) + { + auto vector_id = df::job_item_vector_id::IN_PLAY; + auto job_item = job_items[job_item_idx]; + if (job_item->vector_id) + vector_id = job_item->vector_id; + auto bucket = getBucket(*job_item, pb.getFilters()); + for (int item_num = 0; item_num < job_item->quantity; ++item_num) + { + int32_t id = bld->id; + tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx)); + debug("added task: %s/%s/%d,%d; " + "%zu vectors, %zu buckets, %zu tasks in bucket", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket.c_str(), id, job_item_idx, tasks.size(), + tasks[vector_id].size(), tasks[vector_id][bucket].size()); + } + } + return true; +} + +PlannedBuilding * Planner::getPlannedBuilding(df::building *bld) +{ + if (!bld || planned_buildings.count(bld->id) == 0) + return NULL; + return &planned_buildings.at(bld->id); } bool Planner::isPlannableBuilding(BuildingTypeKey key) { - return item_for_building_type.count(std::get<0>(key)) > 0; + if (getNumFilters(key) == 0) + return false; + + // restrict supported types to be the same as the previous implementation + switch(std::get<0>(key)) + { + case df::enums::building_type::Armorstand: + case df::enums::building_type::Bed: + case df::enums::building_type::Chair: + case df::enums::building_type::Coffin: + case df::enums::building_type::Door: + case df::enums::building_type::Floodgate: + case df::enums::building_type::Hatch: + case df::enums::building_type::GrateWall: + case df::enums::building_type::GrateFloor: + case df::enums::building_type::BarsVertical: + case df::enums::building_type::BarsFloor: + case df::enums::building_type::Cabinet: + case df::enums::building_type::Box: + case df::enums::building_type::Weaponrack: + case df::enums::building_type::Statue: + case df::enums::building_type::Slab: + case df::enums::building_type::Table: + case df::enums::building_type::WindowGlass: + case df::enums::building_type::AnimalTrap: + case df::enums::building_type::Chain: + case df::enums::building_type::Cage: + case df::enums::building_type::TractionBench: + return true; + default: + return false; + } } Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) @@ -535,84 +673,250 @@ Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) return ItemFiltersWrapper(default_item_filters[key]); } -void Planner::doCycle() +// precompute a bitmask with bad item flags +struct BadFlags { - debug("Running Cycle"); - if (planned_buildings.size() == 0) - return; - - debug("Planned count: %zu", planned_buildings.size()); + uint32_t whole; - gather_available_items(); - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + BadFlags() { - if (building_iter->isValid()) - { - auto type = building_iter->getBuilding()->getType(); - debug("Trying to allocate %s", enum_item_key_str(type)); + df::item_flags flags; + #define F(x) flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(in_job); + F(owned); F(in_chest); F(removed); F(encased); + #undef F + whole = flags.whole; + } +}; - auto required_item_type = item_for_building_type[type]; - auto items_vector = &available_item_vectors[required_item_type]; - if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) - { - debug("Unable to allocate an item"); - ++building_iter; - continue; - } - } - debug("Removing building plan"); - building_iter->remove(); - building_iter = planned_buildings.erase(building_iter); +static bool itemPassesScreen(df::item * item) +{ + static BadFlags bad_flags; + return !(item->flags.whole & bad_flags.whole) + && !item->isAssignedToStockpile() + // TODO: make this configurable + && !(item->getType() == df::item_type::BOX && item->isBag()); +} + +static bool matchesFilters(df::item * item, + df::job_item * job_item, + const ItemFilter & item_filter) +{ + if (job_item->item_type > -1 && job_item->item_type != item->getType()) + return false; + + if (job_item->item_subtype > -1 && + job_item->item_subtype != item->getSubtype()) + return false; + + if (job_item->has_tool_use > df::tool_uses::NONE + && !item->hasToolUse(job_item->has_tool_use)) + return false; + + return DFHack::Job::isSuitableItem( + job_item, item->getType(), item->getSubtype()) + && DFHack::Job::isSuitableMaterial( + job_item, item->getMaterial(), item->getMaterialIndex()) + && item_filter.matches(item); +} + +// note that this just removes the PlannedBuilding. the tasks will get dropped +// as we discover them in the tasks queues and they fail their isValid() check. +// this "lazy" task cleaning algorithm works because there is no way to +// re-register a building once it has been removed -- if it fails isValid() +// then it has either been built or desroyed. therefore there is no chance of +// duplicate tasks getting added to the tasks queues. +void Planner::unregisterBuilding(int32_t id) +{ + if (planned_buildings.count(id) > 0) + { + planned_buildings.at(id).remove(); + planned_buildings.erase(id); } } -void Planner::gather_available_items() +static bool isJobReady(df::job * job) { - debug("Gather available items"); - for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) + int needed_items = 0; + for (auto job_item : job->job_items) { needed_items += job_item->quantity; } + if (needed_items) { - iter->second.clear(); + debug("building needs %d more item(s)", needed_items); + return false; } + return true; +} - // Precompute a bitmask with the bad flags - df::item_flags bad_flags; - bad_flags.whole = 0; +static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) +{ + // we want the items in the opposite order of the filters + return a->job_item_idx > b->job_item_idx; +} - #define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); - #undef F +// this function does not remove the job_items since their quantity fields are +// now all at 0, so there is no risk of having extra items attached. we don't +// remove them to keep the "finalize with buildingplan active" path as similar +// as possible to the "finalize with buildingplan disabled" path. +static void finalizeBuilding(df::building * bld) +{ + debug("finalizing building %d", bld->id); + auto job = bld->jobs[0]; - std::vector &items = df::global::world->items.other[df::items_other_id::IN_PLAY]; + // sort the items so they get added to the structure in the correct order + std::sort(job->items.begin(), job->items.end(), job_item_idx_lt); - for (size_t i = 0; i < items.size(); i++) + // derive the material properties of the building and job from the first + // applicable item, though if any boulders are involved, it makes the whole + // structure "rough". + bool rough = false; + for (auto attached_item : job->items) { - df::item *item = items[i]; - - if (item->flags.whole & bad_flags.whole) - continue; - - df::item_type itype = item->getType(); - if (!is_relevant_item_type[itype]) - continue; + df::item *item = attached_item->item; + rough = rough || item->getType() == item_type::BOULDER; + if (bld->mat_type == -1) + { + bld->mat_type = item->getMaterial(); + job->mat_type = bld->mat_type; + } + if (bld->mat_index == -1) + { + bld->mat_index = item->getMaterialIndex(); + job->mat_index = bld->mat_index; + } + } - if (itype == df::item_type::BOX && item->isBag()) - continue; //Skip bags + if (bld->needsDesign()) + { + auto act = (df::building_actual *)bld; + if (!act->design) + act->design = new df::building_design(); + act->design->flags.bits.rough = rough; + } - if (item->flags.bits.artifact) - continue; + // we're good to go! + job->flags.bits.suspend = false; + Job::checkBuildingsNow(); +} - if (item->flags.bits.in_job || - item->isAssignedToStockpile() || - item->flags.bits.owned || - item->flags.bits.in_chest) +void Planner::popInvalidTasks(std::queue> & task_queue) +{ + while (!task_queue.empty()) + { + auto & task = task_queue.front(); + auto id = task.first; + if (planned_buildings.count(id) > 0) { - continue; + PlannedBuilding & pb = planned_buildings.at(id); + if (pb.isValid() && + pb.getBuilding()->jobs[0]->job_items[task.second]->quantity) + { + break; + } } + debug("discarding invalid task: bld=%d, job_item_idx=%d", + id, task.second); + task_queue.pop(); + unregisterBuilding(id); + } +} - available_item_vectors[itype].push_back(item); +void Planner::doCycle() +{ + debug("running cycle for %zu registered buildings", + planned_buildings.size()); + for (auto it = tasks.begin(); it != tasks.end();) + { + auto & buckets = it->second; + auto other_id = ENUM_ATTR(job_item_vector_id, other, it->first); + auto item_vector = df::global::world->items.other[other_id]; + debug("matching %zu items in vector %s against %zu buckets", + item_vector.size(), + ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), + buckets.size()); + for (auto item_it = item_vector.rbegin(); + item_it != item_vector.rend(); + ++item_it) + { + auto item = *item_it; + if (!itemPassesScreen(item)) + continue; + for (auto bucket_it = buckets.begin(); bucket_it != buckets.end();) + { + auto & task_queue = bucket_it->second; + popInvalidTasks(task_queue); + if (task_queue.empty()) + { + debug("removing empty bucket: %s/%s; %zu buckets left", + ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), + bucket_it->first.c_str(), + buckets.size() - 1); + bucket_it = buckets.erase(bucket_it); + continue; + } + auto & task = task_queue.front(); + auto id = task.first; + auto & pb = planned_buildings.at(id); + auto building = pb.getBuilding(); + auto job = building->jobs[0]; + auto filter_idx = task.second; + if (matchesFilters(item, job->job_items[filter_idx], + pb.getFilters()[filter_idx]) + && DFHack::Job::attachJobItem(job, item, + df::job_item_ref::Hauled, filter_idx)) + { + MaterialInfo material; + material.decode(item); + ItemTypeInfo item_type; + item_type.decode(item); + debug("attached %s %s to filter %d for %s(%d): %s/%s", + material.toString().c_str(), + item_type.toString().c_str(), + filter_idx, + ENUM_KEY_STR(building_type, building->getType()).c_str(), + id, + ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), + bucket_it->first.c_str()); + // keep quantity aligned with the actual number of remaining + // 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(); + if (isJobReady(job)) + { + finalizeBuilding(building); + unregisterBuilding(id); + } + if (task_queue.empty()) + { + debug( + "removing empty item bucket: %s/%s; %zu remaining", + ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), + bucket_it->first.c_str(), + buckets.size() - 1); + buckets.erase(bucket_it); + } + // we found a home for this item; no need to look further + break; + } + ++bucket_it; + } + if (buckets.empty()) + break; + } + if (buckets.empty()) + { + debug("removing empty vector: %s; %zu vectors left", + ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), + tasks.size() - 1); + it = tasks.erase(it); + } + else + ++it; } + debug("cycle done; %zu registered buildings left", + planned_buildings.size()); } Planner planner; diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index cda217d81..22d0487c3 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "df/building.h" @@ -16,8 +17,8 @@ public: ItemFilter(); void clear(); - bool deserialize(DFHack::PersistentDataItem &config); - void serialize(DFHack::PersistentDataItem &config) const; + bool deserialize(std::string ser); + std::string serialize() const; void addMaterialMask(uint32_t mask); void clearMaterialMask(); @@ -40,6 +41,9 @@ public: bool matches(df::item *item) const; private: + // remove friend declaration when we no longer need v1 deserialization + friend void migrateV1ToV2(); + df::dfhack_material_category mat_mask; std::vector materials; df::item_quality min_quality; @@ -59,9 +63,6 @@ public: PlannedBuilding(df::building *building, const std::vector &filters); PlannedBuilding(DFHack::PersistentDataItem &config); - bool assignClosestItem(std::vector *items_vector); - bool assignItem(df::item *item); - bool isValid() const; void remove(); @@ -71,8 +72,8 @@ public: private: DFHack::PersistentDataItem config; df::building *building; - df::building::key_field_type building_id; - std::vector filters; + const df::building::key_field_type building_id; + const std::vector filters; }; // building type, subtype, custom @@ -103,7 +104,6 @@ public: std::vector &item_filters; }; - void initialize(); void reset(); void addPlannedBuilding(df::building *bld); @@ -117,15 +117,17 @@ public: void doCycle(); private: - std::map item_for_building_type; std::unordered_map, BuildingTypeKeyHash> default_item_filters; - std::map> available_item_vectors; - std::map is_relevant_item_type; //Needed for fast check when looping over all items - std::vector planned_buildings; - - void gather_available_items(); + // building id -> PlannedBuilding + std::unordered_map planned_buildings; + // vector id -> filter bucket -> queue of (building id, job_item index) + std::map>>> tasks; + + bool registerTasks(PlannedBuilding &plannedBuilding); + void unregisterBuilding(int32_t id); + void popInvalidTasks(std::queue> &task_queue); }; extern Planner planner; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 703e567b7..58c911dcf 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,5 +1,3 @@ -#include - #include "df/entity_position.h" #include "df/interface_key.h" #include "df/ui_build_selector.h" @@ -17,7 +15,7 @@ #include "buildingplan-lib.h" DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.15 +#define PLUGIN_VERSION 2.0 REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(world); @@ -287,6 +285,33 @@ static bool is_planmode_enabled(BuildingTypeKey key) return planmode_enabled[key] || quickfort_mode; } +static std::string get_item_label(const BuildingTypeKey &key, int item_idx) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "get_item_label")) + return "Failed push"; + + Lua::Push(L, std::get<0>(key)); + Lua::Push(L, std::get<1>(key)); + Lua::Push(L, std::get<2>(key)); + Lua::Push(L, item_idx); + + if (!Lua::SafeCall(out, L, 4, 1)) + return "Failed call"; + + const char *s = lua_tostring(L, -1); + if (!s) + return "No string"; + + lua_pop(L, 1); + return s; +} + static bool construct_planned_building() { auto L = Lua::Core::State; @@ -318,6 +343,16 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; + // no non-static fields allowed (according to VTableInterpose.h) + static df::building *bld; + static PlannedBuilding *pb; + static int filter_count; + static int filter_idx; + + // logic is reversed since we're starting at the last filter + bool hasNextFilter() const { return filter_idx > 0; } + bool hasPrevFilter() const { return filter_idx + 1 < filter_count; } + bool isInPlannedBuildingQueryMode() { return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding || @@ -325,21 +360,45 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest planner.getPlannedBuilding(world->selected_building); } + // reinit static fields when selected building changes + void initStatics() + { + df::building *cur_bld = world->selected_building; + if (bld != cur_bld) + { + bld = cur_bld; + pb = planner.getPlannedBuilding(bld); + filter_count = pb->getFilters().size(); + filter_idx = filter_count - 1; + } + } + bool handleInput(set *input) { if (!isInPlannedBuildingQueryMode()) return false; + initStatics(); + if (input->count(interface_key::SUSPENDBUILDING)) return true; // Don't unsuspend planned buildings if (input->count(interface_key::DESTROYBUILDING)) { - // remove persistent data and allow the parent to handle the key - // so the building is removed - planner.getPlannedBuilding(world->selected_building)->remove(); + // remove persistent data + pb->remove(); + // still allow the building to be removed + return false; } - return false; + // ctrl+Right + if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter()) + --filter_idx; + // ctrl+Left + else if (input->count(interface_key::A_MOVE_W_DOWN) && hasPrevFilter()) + ++filter_idx; + else + return false; + return true; } DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) @@ -355,6 +414,8 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest if (!isInPlannedBuildingQueryMode()) return; + initStatics(); + // Hide suspend toggle option auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; @@ -363,10 +424,13 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest Screen::Pen pen(' ', COLOR_BLACK); Screen::fillRect(pen, x, y, dims.menu_x2, y); - // all current buildings only have one filter - auto & filter = planner.getPlannedBuilding(world->selected_building)->getFilters()[0]; + auto & filter = pb->getFilters()[filter_idx]; y = 24; - OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin); + std::string item_label = + stl_sprintf("Item %d of %d", filter_count - filter_idx, filter_count); + OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin + 1); + OutputString(COLOR_WHITE, x, y, item_label.c_str(), true, left_margin + 1); + OutputString(COLOR_WHITE, x, y, get_item_label(toBuildingTypeKey(bld), filter_idx).c_str(), true, left_margin); ++y; OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin); @@ -380,13 +444,35 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest auto filters = filter.getMaterials(); for (auto it = filters.begin(); it != filters.end(); ++it) OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin); + + ++y; + if (hasPrevFilter()) + OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, left_margin); + if (hasNextFilter()) + OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, left_margin); } }; +df::building * buildingplan_query_hook::bld; +PlannedBuilding * buildingplan_query_hook::pb; +int buildingplan_query_hook::filter_count; +int buildingplan_query_hook::filter_idx; + struct buildingplan_place_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; + // no non-static fields allowed (according to VTableInterpose.h) + static BuildingTypeKey key; + static std::vector::reverse_iterator filter_rbegin; + static std::vector::reverse_iterator filter_rend; + static std::vector::reverse_iterator filter; + static int filter_count; + static int filter_idx; + + bool hasNextFilter() const { return filter + 1 != filter_rend; } + bool hasPrevFilter() const { return filter != filter_rbegin; } + bool isInPlannedBuildingPlacementMode() { return ui->main.mode == ui_sidebar_mode::Build && @@ -395,6 +481,22 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest planner.isPlannableBuilding(toBuildingTypeKey(ui_build_selector)); } + // reinit static fields when selected building type changes + void initStatics() + { + BuildingTypeKey cur_key = toBuildingTypeKey(ui_build_selector); + if (key != cur_key) + { + key = cur_key; + auto wrapper = planner.getItemFilters(key); + filter_rbegin = wrapper.rbegin(); + filter_rend = wrapper.rend(); + filter = filter_rbegin; + filter_count = wrapper.get().size(); + filter_idx = filter_count - 1; + } + } + bool handleInput(set *input) { if (!isInPlannedBuildingPlacementMode()) @@ -402,6 +504,8 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest show_help = false; return false; } + + initStatics(); if (in_dummy_screen) { @@ -425,7 +529,6 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest show_help = true; } - BuildingTypeKey key = toBuildingTypeKey(ui_build_selector); if (input->count(interface_key::CUSTOM_SHIFT_P)) { planmode_enabled[key] = !planmode_enabled[key]; @@ -453,8 +556,6 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest return true; } - // all current buildings only have one filter - auto filter = planner.getItemFilters(key).rbegin(); if (input->count(interface_key::CUSTOM_SHIFT_M)) Screen::show(dts::make_unique(*filter), plugin_self); else if (input->count(interface_key::CUSTOM_Q)) @@ -467,6 +568,18 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest filter->incMaxQuality(); else if (input->count(interface_key::CUSTOM_SHIFT_D)) filter->toggleDecoratedOnly(); + // ctrl+Right + else if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter()) + { + ++filter; + --filter_idx; + } + // ctrl+Left + else if (input->count(interface_key::A_MOVE_W_DOWN) && hasPrevFilter()) + { + --filter; + ++filter_idx; + } else return false; return true; @@ -481,7 +594,6 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest DEFINE_VMETHOD_INTERPOSE(void, render, ()) { bool plannable = isInPlannedBuildingPlacementMode(); - BuildingTypeKey key = toBuildingTypeKey(ui_build_selector); if (plannable && is_planmode_enabled(key)) { if (ui_build_selector->stage < 1) @@ -506,6 +618,8 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest if (!plannable) return; + initStatics(); + auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; @@ -539,8 +653,13 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest if (!is_planmode_enabled(key)) return; - auto filter = planner.getItemFilters(key).rbegin(); y += 2; + std::string title = + stl_sprintf("Filter for Item %d of %d:", + filter_count - filter_idx, filter_count); + OutputString(COLOR_WHITE, x, y, title.c_str(), true, left_margin + 1); + OutputString(COLOR_WHITE, x, y, get_item_label(key, filter_idx).c_str(), true, left_margin); + OutputHotkeyString(x, y, "Min Quality: ", "qw"); OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); @@ -554,9 +673,22 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it) OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); + + y += 2; + if (hasPrevFilter()) + OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, left_margin); + if (hasNextFilter()) + OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, left_margin); } }; +BuildingTypeKey buildingplan_place_hook::key; +std::vector::reverse_iterator buildingplan_place_hook::filter_rbegin; +std::vector::reverse_iterator buildingplan_place_hook::filter_rend; +std::vector::reverse_iterator buildingplan_place_hook::filter; +int buildingplan_place_hook::filter_count; +int buildingplan_place_hook::filter_idx; + struct buildingplan_room_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -701,7 +833,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector = #filters then + return 'Invalid index' + end + local filter = filters[#filters-reverse_idx] + if filter.has_tool_use then + return to_title_case(df.tool_uses[filter.has_tool_use]) + end + if filter.item_type then + return to_title_case(df.item_type[filter.item_type]) + end + if filter.flags2 and filter.flags2.building_material then + if filter.flags2.fire_safe then + return "Fire-safe building material"; + end + if filter.flags2.magma_safe then + return "Magma-safe building material"; + end + return "Generic building material"; + end + if filter.vector_id then + return to_title_case(df.job_item_vector_id[filter.vector_id]) + end + return "Unknown"; +end + -- needs the core suspended function construct_building_from_ui_state() local uibs = df.global.ui_build_selector From 09fbaba72609d4f98e117f925d517e42841f7de2 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 16 Oct 2020 14:23:35 -0700 Subject: [PATCH 018/112] buildingplan: support all building types Allow buildingplan to handle all building types, update the docs, and add in little extra fixes to ensure all the new types work correctly. --- data/blueprints/library/dreamfort.csv | 54 ++++++++--------- docs/Plugins.rst | 39 +++++++++++-- docs/changelog.txt | 9 +++ docs/guides/quickfort-user-guide.rst | 83 ++++++++++++++++----------- library/modules/Buildings.cpp | 15 +++++ plugins/buildingplan-planner.cpp | 52 ++++------------- plugins/lua/buildingplan.lua | 22 ++++--- 7 files changed, 156 insertions(+), 118 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 2eb98607f..8924df3c6 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -2,26 +2,28 @@ Welcome to the Dreamfort Walkthrough! It can be difficult applying a set of blueprints that you did not write yourself. This walkthrough will guide you through the high-level steps of building Dreamfort. "" -"The final fort will have a walled-in area on the surface for livestock, trading, and military. Be sure to bring some blocks with you for the initial set of workshops! One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall and dormitory for use until you get the services and apartment levels set up." +"The final fort will have a walled-in area on the surface for livestock, trading, and military. Be sure to bring some blocks with you for the initial set of workshops! One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall and dormitory for use until you get the services and beds levels set up." "" "Beyond those two, the other layers can be built in any order, at any z-level, according to your preference and the layout peculiarities of your embark site:" "- The industry level has a compact, but complete set of workshops and stockpiles (minus what is already provided on the farming level)" "- The services level has dining, hospital, and justice services, including a well system. This level is 4 z-levels deep." -"- The guildhall level has many rooms for building libraries, temples, and guildhalls" +"- The guildhall level has many empty rooms for building libraries, temples, and guildhalls" - The suites level has fancy rooms for your nobles - The apartments level(s) has small but well-furnished bedrooms for your other dwarves "" +"Run the ""help"" blueprints for each level for more details." +"" "Dreamfort has a central staircase-based design. Choose a tile for your staircase on the surface in a nice, flat area. For all blueprints, the cursor will start on this tile on the z-level where you want to apply the blueprint." "" -"Blueprints that require manual steps (like 'assign animals to pasture') will leave a message telling you so after you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" on specific blueprints to start manufacturing needed items." +"Blueprints that require manual steps (like 'assign minecart to hauling route') will leave a message telling you so after you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" on specific blueprints to start manufacturing needed items." "" "Directly after embark, apply /surface1 on a flat area on the surface and /industry1 somewhere underground (but not immediately below the surface -- that will be for the farming level). Work your way through the steps for those levels: apply /surface2 when /surface1 is done, /industry2 when /industry1 is done, etc. Once you channel out parts of the surface with /surface3, you can start the farming sequence with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." "" -"This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, including full lists of their features, it will be easier to look at the .xlsx files than this giant .csv. You can view them online at: https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP" +"This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, it will be easier to look at the online spreadsheets than this giant .csv. You can view them at: https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP" You are welcome to copy those files and make your own modifications! "#dreamfort.csv is generated with the following command: for fname in dreamfort*.xlsx; do xlsx2csv -a -p '' $fname; done | sed 's/,*$//'" -#notes label(surface_readme) +#notes label(surface_help) "Sets up a large, protected entrance to your fort in a flat area on the surface." "" Features: @@ -46,7 +48,7 @@ Once the area is clear, continue with /surface2.) clear an area and set up pastu clear_small/surface_clear_small zones/surface_zones "" -"#meta label(surface2) start(staircase center) message(This would be a good time to queue manager orders for /surface4, /surface5, and /surface6. If you want a consistent color for your walls, remember to set the rock material for the manager orders for blocks. +"#meta label(surface2) start(staircase center) message(This would be a good time to queue manager orders for /surface4, /surface5, and /surface6. If you want a consistent color for your walls, remember to set the rock material in the buildingplan UI and in the manager orders for blocks. Once the whole area is clear, continue with /surface3.) set up starting workshops/stockpiles and clear a larger area. if you didn't bring blocks, temporarily turn off the buildings_use_blocks setting so you can use wood or boulders" build_start/surface_build_start place_start/surface_place_start @@ -54,14 +56,14 @@ query_start/surface_query_start clear/surface_clear pick/surface_pick "" -"#meta label(surface3) start(staircase center) message(Once the channels are dug out and you have around 650 blocks, continue with /surface4. You can also start digging out the sub-surface farming level once the channels are done.) channel to prevent miasma in the sub-surface farming level" +"#meta label(surface3) start(staircase center) message(Once the channels are dug out, continue with /surface4. You can also start digging out the sub-surface farming level once the channels are done.) channel to prevent miasma in the sub-surface farming level" dig/surface_channel "" -"#meta label(surface4) start(staircase center) message(Once floors and walls are built and you have completed the manager orders for /surface5, continue with /surface5.) cover up the holes with flooring and build walls" +"#meta label(surface4) start(staircase center) message(Once floors and walls are built, continue with /surface5.) cover up the holes with flooring and build walls" build_floors/surface_floors build_walls/surface_walls "" -"#meta label(surface5) start(staircase center) message(Once you have around 1,050 blocks, continue with /surface6) build gates, furniture, the trade depot, and traps" +"#meta label(surface5) start(staircase center) message(Once buildings have been constructed, continue with /surface6) build gates, furniture, the trade depot, and traps" build_bridges/surface_gates build_buildings/surface_buildings "" @@ -820,7 +822,7 @@ p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p, -#notes label(farming_readme) +#notes label(farming_help) "Sets up farming, food storage, and related industries. Also provides post-embark necessities that can later be disassembed." "" Features: @@ -844,7 +846,7 @@ Screw Press Manual steps you have to take: Assign the office to your manager Assign a minecart to your quantum garbage stockpile hauling route -"if the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level" +"If the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level" "#dig label(farming1) start(23; 25; staircase center) message(This would be a good time to queue up manager orders for /farming2. Once the area is dug out, continue with /farming2.)" @@ -985,13 +987,13 @@ query_stockpiles/farming_query_stockpiles #place label(farming_place) start(23; 25) hidden() use the meta blueprints for normal application -,,,,,,,,,`,`,`,`,`,`,`,`,f(3x6),`,`,,`,,`,,f(14x16),`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,`,`,`,`,`,`,`,`,f(3x6),,,,`,,`,,f(14x16),,,,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,f(8x1),`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,b(8x1),`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,f(8x1),,,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,b(8x1),,,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,`,,,`,,,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,f(14x10),`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,f(14x10),,,,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` @@ -1002,7 +1004,7 @@ query_stockpiles/farming_query_stockpiles ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,,,,`,,,,`,,,,`,`,`,,,`,,,,` -,,,,,,,,u,u,u,u,`,`,`,u(5x3),`,`,`,`,,`,`,`,,`,`,`,,`,`,` +,,,,,,,,u,u,u,u,`,`,`,u(5x3),,,`,`,,`,`,`,,`,`,`,,`,`,` ,,,,,,,,g,`,`,u,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,g,g,g,g,`,`,`,`,`,`,`,`,,`,,`,,`,`,`,,`,`,` ,,,,,,,,,`,,,,`,,,,`,,,,`,`,`,,,`,,,,` @@ -1010,20 +1012,20 @@ query_stockpiles/farming_query_stockpiles ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,`,`,`,`,` ,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,,,,,`,,,,,` -,,,,,,,,,`,`,`,,f(7x2),`,`,`,`,`,`,,`,,`,,r(2x3),`,`,`,`,,f(1x3),`,`,` +,,,,,,,,,`,`,`,,f(7x2),,,`,`,`,`,,`,,`,,r(2x3),,,`,`,,f(1x3),,,` ,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,` ,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,,,,`,,,,,` -,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,ry(14x10),`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,ry(14x10),,,,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,`,`,`,,f(7x2),`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,`,`,`,,f(7x2),,,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,ry(3x3),`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,,,,,ry(3x3),,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,ry(1x1),,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,,,,,,ry,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` "#query label(farming_query_stockpiles) start(23; 25) hidden() message(remember to: @@ -1116,7 +1118,7 @@ query_stockpiles/farming_query_stockpiles ,,,,,,,,,,,,,,,,,,,,,,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -#notes label(industry_readme) +#notes label(industry_help) Sets up workshops for all non-farming industries "" Features: @@ -1303,7 +1305,7 @@ query/industry_query ,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,` -#notes label(services_readme) +#notes label(services_help) "Sets up public services (dining, hospital, etc.)" "" Features: @@ -1319,7 +1321,7 @@ Manual steps you have to take: "If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." "Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)" "Convert the bath pit zones to pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." -"Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experience with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." +"Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." "#dig label(services1) start(23; 22; staircase center) message(This would be a good time to queue manager orders for /services2. Once the area is dug out, continue with /services2.)" @@ -1633,7 +1635,7 @@ query_stockpiles/services_query_stockpiles ,,`,`,`,`,`,`,,`,`,`,`,`,` -#notes label(guildhall_readme) +#notes label(guildhall_help) "Multiple 7x7 rooms for guildhalls, temples, libraries, etc." "" Features: @@ -1738,7 +1740,7 @@ Features: ,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,` -#notes label(beds_readme) +#notes label(beds_help) Suites for nobles and apartments for the teeming masses "" Features: diff --git a/docs/Plugins.rst b/docs/Plugins.rst index a690ddbf3..3cff80c9c 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -706,15 +706,44 @@ enabled materials, you should be able to place complex constructions more conven buildingplan ============ When active (via ``enable buildingplan``), this plugin adds a planning mode for -furniture placement. You can then place furniture and other buildings before -the required materials are available, and the job will be unsuspended when -the item is created. +building placement. You can then place furniture, constructions, and other buildings +before the required materials are 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. -Very useful when combined with `workflow` - you can set a constraint +This is very useful when combined with `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. The plugins then take over and fulfill the orders, +as many as you like. The plugins then take over and fulfill the orders, with minimal space dedicated to stockpiles. +Item filtering +-------------- + +While placing a building, you can set filters for what materials you want the building made +out of, what quality you want the component items to be, and whether you want the items to +be decorated. + +If a building type takes more than one item to construct, use Ctrl+Left and Ctrl+Right to +select the item that you want to set filters for. Any filters that you set will be used for +all buildings of the selected type from that point onward (until you set a new filter or +clear the current one). + +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 Quickfort mode to be enabled. The `quickfort` script +will successfully integrate with buildingplan as long as the buildingplan plugin is enabled. + .. _confirm: confirm diff --git a/docs/changelog.txt b/docs/changelog.txt index 7f289c36d..dbe296c85 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,8 +34,17 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## Fixes +- `buildingplan`: artifacts are now successfully matched when max quality is set to ``artifacts`` - `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences +## Misc Improvements +- `buildingplan`: all buildings, furniture, and constructions are now supported (except for the few building types not supported by dfhack itself) +- `buildingplan`: now respects building job_item filters when matching items +- `buildingplan`: default filter setting for max quality changed from ``artifact`` to ``masterwork`` + +## API +- `buildingplan`: added Lua interface API + # 0.47.04-r3 ## New Plugins diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 76485a985..bef85b161 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -69,7 +69,8 @@ Features - Build mode - - DFHack buildingplan integration + - DFHack buildingplan integration, so you can place buildings before + manufacturing all required source materials - Designate complete constructions at once, without having to wait for each tile to become supported before you can build it - Automatic expansion of building footprints to their minimum dimensions, so @@ -1022,32 +1023,49 @@ allowing for much easier viewing and editing. Buildingplan integration ------------------------ -Buildingplan is a DFHack plugin that keeps jobs in a suspended state until the -materials required for the job are available. This prevents a building -designation from being canceled when a dwarf picks up the job but can't find the -materials. - -For all types that buildingplan supports, quickfort using buildingplan to manage -construction. Buildings are still constructed immediately if you have the -materials, but you now have the freedom to apply build blueprints before you -manufacture all required materials, and the jobs will be fulfilled as the -materials become available. - -If a ``#build`` blueprint only refers to supported types, the buildingplan -integration pairs well with the `workflow` plugin, which can build items a few -at a time continuously as long as they are needed. For building types that are -not yet supported by buildingplan, a good pattern to follow is to first run -``quickfort orders`` on the ``#build`` blueprint to manufacture all the required -items, then apply the blueprint itself. - -See the `buildingplan documentation ` for more information. As of -this writing, buildingplan only supports basic furniture. +Buildingplan is a DFHack plugin that keeps building construction jobs in a +suspended state until the materials required for the job are available. This +prevents a building designation from being canceled when a dwarf picks up the +job but can't find the materials. + +As long as the `buildingplan` plugin is enabled, quickfort will use it to manage +construction. The buildingplan plugin also has an "enabled" setting for each +building type, but that setting only applies to the buildingplan user interface; +quickfort will always use buildingplan to manage everything designated in a +``#build`` blueprint. + +However, quickfort *does* use buildingplan's filters for each building type. For +example, you can use the buildingplan UI to set the stone you want your walls +made out of. Or you can specify that all buildingplan-managed tables must be of +Masterful quality. The current filter settings are saved with planned buildings +when the ``#build`` blueprint is run. This means you can set the filters the way +you want for one blueprint, run the blueprint, and then freely change them again +for the next blueprint, even if the first set of buildings haven't been built +yet. + +Note that buildings are still constructed immediately if you already have the +materials. However, with the buildingplan integration you now have the freedom +to apply ``#build`` blueprints before you manufacture the resources. The +construction jobs will be fulfilled as the materials become available. + +Since it can be difficult to figure out exactly what source materials you need +for a ``#build`` blueprint, quickfort supplies the ``orders`` command. It +enqueues manager orders for everything that the buildings in a ``#build`` +blueprint require. See the next section for more details on this. + +Alternately, if you know you only need a few types of items, the `workflow` +plugin can be configured to build those items continuously for as long as they +are needed. + +If the buildingplan plugin is not enabled, run ``quickfort orders`` first and +make sure all manager orders are fulfilled before applying a ``#build`` +blueprint. Generating manager orders ------------------------- Quickfort can generate manager orders to make sure you have the proper items in -stock to apply a ``#build`` blueprint. +stock for a ``#build`` blueprint. Many items can be manufactured from different source materials. Orders will always choose rock when it can, then wood, then cloth, then iron. You can always @@ -1066,7 +1084,9 @@ If you want your constructions to be in a consistent color, be sure to choose a rock type for all of your 'Make rock blocks' orders by selecting the order and hitting ``d``. You might want to set the rock type for other non-block orders to something different if you fear running out of the type of rock that you want to -use for blocks. +use for blocks. You should also set the `buildingplan` material filter for +construction building types to that type of rock as well so other random blocks +you might have lying around aren't used. There are a few building types that will generate extra manager orders for related materials: @@ -1105,23 +1125,16 @@ Tips and tricks Caveats and limitations ----------------------- -- Buildings will be designated regardless of whether you have the required - materials, but if materials are not available when the construction job is - picked up by a dwarf, the buildings will be canceled and the designations - will disappear. Until the buildingplan plugin can be extended to support all - building types, you should use ``quickfort orders`` to pre-manufacture all - the materials you need for a ``#build`` blueprint before you apply it. - - If you use the ``jugs`` alias in your ``#query``-mode blueprints, be aware that there is no way to differentiate jugs from other types of tools in the game. Therefore, ``jugs`` stockpiles will also take nest boxes and other tools. The only workaround is not to have other tools lying around in your fort. -- Likewise for bags. The game does not differentiate between empty and full - bags, so you'll get bags of gypsum power and sand in your bags stockpile - unless you avoid collecting sand and are careful to assign all your gypsum to - your hospital. +- Likewise for the ``bags`` alias. The game does not differentiate between + empty and full bags, so you'll get bags of gypsum power and sand in your bags + stockpile unless you avoid collecting sand and are careful to assign all your + gypsum to your hospital. - Weapon traps and upright spear/spike traps can currently only be built with a single weapon. @@ -1260,7 +1273,7 @@ sheet, like in surface's meta sheet. things to include in messages are: * The name of the next blueprint to apply and when to run it -* Whether quickfort orders should be run for an upcoming step +* Whether quickfort orders could be run for an upcoming step * Any manual actions that have to happen, like assigning minecarts to hauling routes or pasturing animals after creating zones diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 976ade441..6d6d777f7 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -368,10 +368,19 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in obj->bucket_z = bld->z; break; } + case building_type::Workshop: + { + if (VIRTUAL_CAST_VAR(obj, df::building_workshopst, bld)) + obj->profile.max_general_orders = 5; + break; + } case building_type::Furnace: { if (VIRTUAL_CAST_VAR(obj, df::building_furnacest, bld)) + { obj->melt_remainder.resize(df::inorganic_raw::get_vector().size(), 0); + obj->profile.max_general_orders = 5; + } break; } case building_type::Coffin: @@ -419,6 +428,12 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in obj->gate_flags.bits.closed = true; break; } + case building_type::Bridge: + { + if (VIRTUAL_CAST_VAR(obj, df::building_bridgest, bld)) + obj->gate_flags.bits.closed = false; + break; + } default: break; } diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index dcdcfb777..0a714854c 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -495,7 +495,9 @@ void migrateV1ToV2() // remove the v1 record DFHack::World::DeletePersistentData(config); - debug("v1 record successfully migrated"); + debug("v1 %s(%d) record successfully migrated", + ENUM_KEY_STR(building_type, bld->getType()).c_str(), + bld->id); } } @@ -510,13 +512,14 @@ void Planner::reset() std::vector items; DFHack::World::GetPersistentData(&items, planned_building_persistence_key_v2); - debug("found data for %zu planned buildings", items.size()); + debug("found data for %zu planned building(s)", items.size()); for (auto i = items.begin(); i != items.end(); i++) { PlannedBuilding pb(*i); if (!pb.isValid()) { + debug("discarding invalid planned building"); pb.remove(); continue; } @@ -608,7 +611,7 @@ bool Planner::registerTasks(PlannedBuilding & pb) int32_t id = bld->id; tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx)); debug("added task: %s/%s/%d,%d; " - "%zu vectors, %zu buckets, %zu tasks 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(), bucket.c_str(), id, job_item_idx, tasks.size(), tasks[vector_id].size(), tasks[vector_id][bucket].size()); @@ -626,38 +629,7 @@ PlannedBuilding * Planner::getPlannedBuilding(df::building *bld) bool Planner::isPlannableBuilding(BuildingTypeKey key) { - if (getNumFilters(key) == 0) - return false; - - // restrict supported types to be the same as the previous implementation - switch(std::get<0>(key)) - { - case df::enums::building_type::Armorstand: - case df::enums::building_type::Bed: - case df::enums::building_type::Chair: - case df::enums::building_type::Coffin: - case df::enums::building_type::Door: - case df::enums::building_type::Floodgate: - case df::enums::building_type::Hatch: - case df::enums::building_type::GrateWall: - case df::enums::building_type::GrateFloor: - case df::enums::building_type::BarsVertical: - case df::enums::building_type::BarsFloor: - case df::enums::building_type::Cabinet: - case df::enums::building_type::Box: - case df::enums::building_type::Weaponrack: - case df::enums::building_type::Statue: - case df::enums::building_type::Slab: - case df::enums::building_type::Table: - case df::enums::building_type::WindowGlass: - case df::enums::building_type::AnimalTrap: - case df::enums::building_type::Chain: - case df::enums::building_type::Cage: - case df::enums::building_type::TractionBench: - return true; - default: - return false; - } + return getNumFilters(key) >= 1; } Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key) @@ -831,7 +803,7 @@ void Planner::doCycle() auto & buckets = it->second; auto other_id = ENUM_ATTR(job_item_vector_id, other, it->first); auto item_vector = df::global::world->items.other[other_id]; - debug("matching %zu items in vector %s against %zu buckets", + debug("matching %zu item(s) in vector %s against %zu filter bucket(s)", item_vector.size(), ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), buckets.size()); @@ -848,7 +820,7 @@ void Planner::doCycle() popInvalidTasks(task_queue); if (task_queue.empty()) { - debug("removing empty bucket: %s/%s; %zu buckets left", + debug("removing empty bucket: %s/%s; %zu bucket(s) left", ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), bucket_it->first.c_str(), buckets.size() - 1); @@ -891,7 +863,7 @@ void Planner::doCycle() if (task_queue.empty()) { debug( - "removing empty item bucket: %s/%s; %zu remaining", + "removing empty item bucket: %s/%s; %zu left", ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), bucket_it->first.c_str(), buckets.size() - 1); @@ -907,7 +879,7 @@ void Planner::doCycle() } if (buckets.empty()) { - debug("removing empty vector: %s; %zu vectors left", + debug("removing empty vector: %s; %zu vector(s) left", ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), tasks.size() - 1); it = tasks.erase(it); @@ -915,7 +887,7 @@ void Planner::doCycle() else ++it; } - debug("cycle done; %zu registered buildings left", + debug("cycle done; %zu registered building(s) left", planned_buildings.size()); } diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 49b80990f..2de7db7d2 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -79,18 +79,16 @@ function construct_building_from_ui_state() custom=uibs.custom_type, pos=pos, width=width, height=height, direction=direction} if err then error(err) end - -- TODO: assign fields for the types that need them. we can't pass them all - -- in to the call to constructBuilding since the unneeded fields will cause - -- errors - --local fields = { - -- friction=uibs.friction, - -- use_dump=uibs.use_dump, - -- dump_x_shift=uibs.dump_x_shift, - -- dump_y_shift=uibs.dump_y_shift, - -- speed=uibs.speed - --} - -- TODO: use quickfort's post_construction_fns? maybe move those functions - -- into the library so they get applied automatically + -- assign fields for the types that need them. we can't pass them all in to + -- the call to constructBuilding since attempting to assign unrelated + -- fields to building types that don't support them causes errors. + for k,v in pairs(bld) do + if k == 'friction' then bld.friction = uibs.friction end + if k == 'use_dump' then bld.use_dump = uibs.use_dump end + if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end + if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end + if k == 'speed' then bld.speed = uibs.speed end + end return bld end From 6c3bac2d6caf7382a88a929afa485a4c3f89487a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 17 Oct 2020 22:20:39 -0700 Subject: [PATCH 019/112] document extended zone configuration syntax --- docs/guides/quickfort-user-guide.rst | 53 +++++++++++++++++++--------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index bef85b161..f4b15969a 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -96,6 +96,7 @@ Features created stockpiles - Automatic splitting of stockpiles and zones that exceed maximum dimension limits + - Full access to all zone settings, such as hospital supply counts - Query mode @@ -478,37 +479,57 @@ It is very common to have stockpiles that accept multiple categories of items or zones that permit more than one activity. Although it is perfectly valid to declare a single-purpose stockpile or zone and then modify it with a ``#query`` blueprint, quickfort also supports directly declaring all the types on the -``#place`` and ``#zone`` blueprints. For example, to declare a 10x10 area that -is a pasture, a fruit picking area, and a meeting area all at once, you could -write: +``#place`` and ``#zone`` blueprints. For example, to declare a 20x10 stockpile +that accepts both corpses and refuse, you could write: + +:: + + #place refuse heap + yr(20x10) + + +And similarly, to declare a zone that is a pasture, a fruit picking area, and a +meeting area all at once: :: #zone main pasture and picnic area nmg(10x10) -And similarly, to declare a stockpile that accepts both corpses and refuse, you -could write: +The order of the individual letters doesn't matter. + +Detailed configuration for zones, such as the pit/pond toggle, can also be set +by mimicking the hotkeys used to set them. Note that gather flags default to +true, so specifying them in a blueprint will turn the toggles off. If you need +to set configuration from multiple zone subscreens, separate the key sections +with ``^``. Note the special syntax for setting hospital supply levels, which +have no in-game hotkeys: :: - #place refuse heap - yr(20x10) + #zone a combination hospital and shrub (but not fruit) gathering zone + gGtf^hH{hospital buckets=5 splints=20}(10x10) -The order of the individual letters doesn't matter. +The valid hospital settings (and their maximum values) are: +:: + + thread (1500000) + cloth (1000000) + splints (100) + crutches (100) + powder (15000) + buckets (100) + soap (15000) + To toggle the ``active`` flag for zones, add an ``a`` character to the string. -For example, to create a *disabled* pit zone (that you later intend to turn into -a pond and carefully fill to 3-depth water): +For example, to create a *disabled* pond zone (that you later intend to +carefully fill with 3-depth water for a dwarven bathtub): :: - #zone disabled future pond zone - pa(1x3) - -Note that while this notation covers most use cases, tweaking low-level zone -parameters, like hospital supply levels or converting between pits and ponds, -must still be done manually or with a ``#query`` blueprint. + #zone disabled pond zone + apPf(1x3) Minecart tracks ~~~~~~~~~~~~~~~ From ea5a5414c93af8f280b0d5c84ec286f741e1d018 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 17 Oct 2020 22:21:50 -0700 Subject: [PATCH 020/112] document (and use) parameterized aliases --- data/quickfort/aliases-common.txt | 62 ++++++++++++++++------------- dfhack-config/quickfort/aliases.txt | 18 ++++++++- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index ed9a34393..d5f52efcf 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -1,8 +1,8 @@ # Common baseline for aliases for quickfort query mode blueprints. # -# Please DO NOT EDIT this file directly. Instead, custom aliases should be added -# to dfhack-config/quickfort/aliases.txt. See that file for syntax -# documentation. +# Please DO NOT EDIT this file directly. It will get overwritten when DFHack +# is updated. Instead, custom aliases should be added to +# dfhack-config/quickfort/aliases.txt. See that file for syntax documentation. # # The aliases in this file were tested in DF 0.47.04 on 2020 Jul 18. # @@ -31,14 +31,15 @@ linksonly: a nocontainers: CE # for configuring stockpiles to give to other nearby stockpiles/workshops -give2up: g{Up 2}& -give2down: g{Down 2}& -give2left: g{Left 2}& -give2right: g{Right 2}& -give10up: g{Up 10}& -give10down: g{Down 10}& -give10left: g{Left 10}& -give10right: g{Right 10}& +give: g{move}& +give2up: {give move={Up 2}} +give2down: {give move={Down 2}} +give2left: {give move={Left 2}} +give2right: {give move={Right 2}} +give10up: {give move={Up 10}} +give10down: {give move={Down 10}} +give10left: {give move={Left 10}} +give10right: {give move={Right 10}} # use to toggle a sequence of stockpile options. for example: {togglesequence 5} togglesequence: &{Down} @@ -58,25 +59,30 @@ quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{furnitureprefix}{e # Run one of the quantumstopfrom* aliases over a track stop that is set to dump # into a quantum stockpile. The alias will set up the stop to accept all types -# (the actual types stored in the quantum stockpile is controlled by the feeder -# stockpile) and link the indicated adjacent feeder stockpile (for example, the -# quantumstopfromsouth alias will link to a feeder stockpile to the South). All -# you need to do afterwards is assign a vehicle to the stop (and optionally -# give the route a name --see the namelastroute* aliases below). The track stop -# does not need to be constructed yet, but the feeder stockpile needs to exist -# so we can link it. -quantumstopprefix: ^hrs&xxx&{enablesequence 17}^ -quantumstopfromeast: {quantumstopprefix}s{Right}p^{Left}^q -quantumstopfromsouth: {quantumstopprefix}s{Down}p^{Up}^q -quantumstopfromwest: {quantumstopprefix}s{Left}p^{Right}^q -quantumstopfromnorth: {quantumstopprefix}s{Up}p^{Down}^q - -# Give a name to the most recently defined route. Keep in mind that names have -# a maximum length of 22 characters. It assumes that the route has exactly one -# stop defined. Use it with the quantumstopfrom* aliases above like this: -# {quantumstopfromeast}{namelastrouteprefix}Trash Dumper{namelastroutesuffix} +# (the actual types stored in the quantum stockpile should be controlled by the +# feeder stockpile) and link the indicated adjacent feeder stockpile. For +# example, the quantumstopfromsouth alias should be used over a track stop set +# to dump to the North and take items from a feeder stockpile one tile to the +# South. All you need to do afterwards is assign a vehicle to the stop. The +# track stop does not need to be constructed yet, but the feeder stockpile needs +# to exist so we can link to it. +# +# Be sure to define the optional 'name' parameter if you want to give your +# quantum hauling routes custom names. Keep in mind that names have a maximum +# length of 22 characters. For example: +# {quantumstopfromsouth name="Trash Dump"} +# +# For several examples of these aliases, see +# https://docs.google.com/spreadsheets/d/1gvTJxxRxZ5V4vXkqwhL-qlr_lXCNt8176TK14m4kSOU namelastrouteprefix: ^h--n namelastroutesuffix: &^q +namelastroute: {namelastrouteprefix}{name}{namelastroutesuffix} +quantumstopprefix: ^hrs&xxx&{enablesequence 17}^ +quantumstopsuffix: ^q{namelastroute} +quantumstopfromeast: {quantumstopprefix}s{Right}p^{Left}{quantumstopsuffix} +quantumstopfromsouth: {quantumstopprefix}s{Down}p^{Up}{quantumstopsuffix} +quantumstopfromwest: {quantumstopprefix}s{Left}p^{Right}{quantumstopsuffix} +quantumstopfromnorth: {quantumstopprefix}s{Up}p^{Down}{quantumstopsuffix} ################################## diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt index 2b90e67c4..2743a39f3 100644 --- a/dfhack-config/quickfort/aliases.txt +++ b/dfhack-config/quickfort/aliases.txt @@ -1,6 +1,6 @@ -# aliases for quickfort query mode blueprints +# Aliases for quickfort #query mode blueprints # -# This file defines custom keycode shortcuts for query mode blueprints. +# This file defines custom keycode shortcuts for #query mode blueprints. # Definitions in this file take precedence over any definitions in the baseline # aliases configuration file at hack/data/quickfort/aliases-common.txt. See that # file for the many useful aliases that are already defined. @@ -69,6 +69,20 @@ # indicating how many times that alias or keycode should be repeated. For # example: "{togglesequence 9}" or "{Down 5}". # +# Finally, you can specify sub-aliases that will only be defined while the +# current alias is being resolved by adding them after the alias name (but +# before the repetition number, if it is specified), for example: +# {quantumstopfromeast name="Trash Dump"} +# The value of the sub-alias 'name' is used by quantumstopfromeast (or one of +# the aliases it calls) to give a useful name to your quantum dump hauling +# route. You can also use this format to temporarily override the value of an +# existing regularly-defined alias. +# +# Sub-aliases must be in one of the following formats: +# subaliasname=valwithnospaces +# subaliasname="val with spaces" +# subaliasname={someotheralias repetitions} +# # Ctrl, Alt, and Shift modifiers can be specified for the next keycode by adding # them into the key sequence. For example, Alt-h is written as "{Alt}h". # From 914c24d873a1ac9d975d6da56e0a27f18b434b84 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 17 Oct 2020 22:22:32 -0700 Subject: [PATCH 021/112] use new quickfort features in dreamfort parameterized aliases and configurable zones --- data/blueprints/library/dreamfort.csv | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 8924df3c6..64d32ce8d 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -1070,7 +1070,7 @@ query_stockpiles/farming_query_stockpiles ,,,,,,,,,,,,,,,,,,,,,bodyparts,linksonly,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,`,give2left,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,{quantumstopfromnorth}{namelastrouteprefix}Trash Dumper{namelastroutesuffix},,,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,,,,,,"{quantumstopfromnorth name=""Trash Dumper""}",,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,,,,,,,,,,,,,,,,,,,quantum,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` @@ -1279,7 +1279,7 @@ query/industry_query ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,{quantum}g{Up}{Left 5}&,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,{quantumstopfromsouth}{namelastrouteprefix}Stoneworker quantum{namelastroutesuffix},`,`,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,"{quantumstopfromsouth name=""Stoneworker quantum""}",`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,otherstone,,,`,`,`,`,`,`,`,`,`,`,`,` ,,miscliquid,`,`,`,`,`,`,`,`,`,`,`,`,`,~,nocontainers,~,`,`,`,`,`,`,`,`,`,`,`,`,`,steelbars ,,nocontainers,`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,` @@ -1287,7 +1287,7 @@ query/industry_query ,,`,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,{cages}{permittraps},,,`,`,`,,,,`,`,`,forbidotherstone,,,`,`,`,`,`,`,`,t{Left 6}{Down}& -,,`,`,`,`,`,`,{quantum}g{Up 10}{Right 4}&,{quantumstopfromeast}{namelastrouteprefix}Goods/Wood quantum{namelastroutesuffix},~,nocontainers,~,`,,`,,`,,`,,`,~,nocontainers,~,{quantumstopfromwest}{namelastrouteprefix}Metalworker quantum{namelastroutesuffix},quantum,`,`,`,`,`,` +,,`,`,`,`,`,`,{quantum}g{Up 10}{Right 4}&,"{quantumstopfromeast name=""Goods/Wood quantum""}",~,nocontainers,~,`,,`,,`,,`,,`,~,nocontainers,~,"{quantumstopfromwest name=""Metalworker quantum""}",quantum,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,{tallow}{permitdye},,,`,`,`,,,,`,`,`,forbidpotash,,,`,`,`,`,`,`,`,t{Left 6}{Up}& ,,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` @@ -1295,7 +1295,7 @@ query/industry_query ,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,craftrefuse,,,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,nocontainers,~,`,`,`,`,`,`,`,`,`,`,`,`,`,coal ,,,,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,nocontainers,`,t{Up 7}&,~,~,~,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,{quantumstopfromnorth}{namelastrouteprefix}Clothier/Bones quantum{namelastroutesuffix},`,`,`,`,`,`,{ironweapons}{permitsteelweapons}{forbidmasterworkweapons}{forbidartifactweapons} +,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,"{quantumstopfromnorth name=""Clothier/Bones quantum""}",`,`,`,`,`,`,{ironweapons}{permitsteelweapons}{forbidmasterworkweapons}{forbidartifactweapons} ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,quantum,`,`,`,`,`,`,{ironarmor}{permitsteelarmor},forbidmasterworkarmor,forbidartifactarmor,~,~,~,~ ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,nocontainers,give2up,t{Up 11}&,~,~,~,~ @@ -1320,7 +1320,7 @@ Garbage dump Manual steps you have to take: "If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." "Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)" -"Convert the bath pit zones to pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." +"Activate the bath pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." "Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." "#dig label(services1) start(23; 22; staircase center) message(This would be a good time to queue manager orders for /services2. Once the area is dug out, continue with /services2.)" @@ -1533,7 +1533,7 @@ query_stockpiles/services_query_stockpiles ,,`,`,`,`,`,`,,`,`,`,`,`,` -#zone label(services_zone) start(23; 22) hidden() use the meta blueprints for normal application +#zone label(services_zone) start(23; 22) hidden() message(activate the bath pond zones when you are ready to fill them with 3-depth water) use the meta blueprints for normal application ,,`,`,`,,,`,`,`,,,`,`,` @@ -1554,9 +1554,9 @@ query_stockpiles/services_query_stockpiles ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,,,,,,,,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` -,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,`,` -,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,pa,`,,`,`,`,`,`,,`,pa,`,,`,`,`,`,`,`,`,`,` -,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,pa,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,apPf(1x3),`,`,`,`,`,`,`,`,`,apPf(1x3),`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,~,`,,`,`,`,`,`,,`,~,`,,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` ,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,d,,,,,,,,`,`,`,`,`,`,`,`,` ,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` From 05d2e5b65c370e3ab467f0e3efe93c82615d3f31 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Oct 2020 13:49:11 -0700 Subject: [PATCH 022/112] update case study: order-dependent aliases since the example I had used no longer exists now that we have parameterized aliases. I had to find another example in the industry blueprints. I made it a proper "tip" and added more explanation as well. --- docs/guides/quickfort-user-guide.rst | 46 ++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index f4b15969a..2dfaa74b1 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1342,11 +1342,6 @@ Hauling routes are notoriously fiddly to set up, but they can be automated with blueprints. Check out the Southern area of the ``#place`` and ``#query`` blueprints for how the quantum garbage dump is configured. -Note that aliases that must be applied in a particular order must appear in the -same cell. Otherwise there are no guarantees for which cell will be processed -first. For example, look at the track stop cells in the ``#query`` blueprint for -how the hauling routes are given names. - The industry_ level: when not to use aliases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1354,7 +1349,7 @@ The industry_ level: when not to use aliases The industry level is densely packed and has more complicated examples of stockpile configurations and quantum dumps. However, what I'd like to call out -are the key sequences that are *not* in aliases. +first are the key sequences that are *not* in aliases. .. topic:: Tip @@ -1374,6 +1369,45 @@ see in the blueprint itself. Also, if you move the workshop, it's easier to fix the stockpile link right there in the blueprint instead of editing the separate aliases.txt file. +There are also good examples in the query blueprint for how to use the +``permit`` and ``forbid`` stockpile aliases. + +.. topic:: Tip + + Put all configuration that must be applied in a particular order in the + same spreadsheet cell. + +Most of the baseline aliases distributed with DFHack fall into one of three +categories: + +# Make a stockpile accept only a particular item type in a category +# Permit an item type, but do not otherwise change the stockpile configuration +# Forbid an item type, but do not otherwise change the stockpile configuration + +If you have a stockpile that covers multiple tiles, it might seem natural to put +one alias per spreadsheet cell. The aliases still all get applied to the +stockpile, and with only one alias per cell, you can just type the alias name +and avoid having to use the messier-looking ``{alias1}{alias2}`` syntax: + +:: + + #query Incorrectly configure a 3x3 food stockpile to accept tallow and dye + tallow + permitdye + +However, in quickfort there are no guarantees about which cell will be +processed first. In the example above, we obviously intend for the food +stockpile to have everything forbidden, then tallow permitted, then dye +permitted. The algorithm could happen to apply them in the opposite order, +though, and we'd end up with dye being permitted, then everything being +forbidden and tallow being enabled. To make sure you always get what you want, +write order-sensitive aliases on the same line: + +:: + + #query Properly configure a 3x3 food stockpile to accept tallow and dye + {tallow}{permitdye} + The services_ level: handling multi-level dig blueprints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 3a200fcd92b394ee67700c1e6750a5f7b39043e3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 20 Oct 2020 01:01:02 -0400 Subject: [PATCH 023/112] Expand `probe` documentation --- docs/Plugins.rst | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index a690ddbf3..f9ce3184c 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -134,7 +134,17 @@ single Lua function, in ``hack/lua/plugins/pathable.lua``: probe ===== -Can be used to determine tile properties like temperature. + +This plugin provides multiple commands that print low-level properties of the +selected objects. + +* ``probe``: prints some properties of the tile selected with :kbd:`k`. Some of + these properties can be passed into `tiletypes`. +* ``cprobe``: prints some properties of the unit selected with :kbd:`v`, as well + as the IDs of any worn items. `gui/gm-unit` and `gui/gm-editor` are more + complete in-game alternatives. +* ``bprobe``: prints some properties of the building selected with :kbd:`q` or + :kbd:`t`. `gui/gm-editor` is a more complete in-game alternative. .. _prospect: .. _prospector: @@ -2488,7 +2498,8 @@ See also `alltraffic`, `filltraffic`, and `restrictice`. tiletypes ========= Can be used for painting map tiles and is an interactive command, much like -`liquids`. If something goes wrong, `fixveins` may help. +`liquids`. Some properties of existing tiles can be looked up with `probe`. If +something goes wrong, `fixveins` may help. The tool works with two set of options and a brush. The brush determines which tiles will be processed. First set of options is the filter, which can exclude From 84973d1fb80906da92477816e4439aa45f6972a0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 20 Oct 2020 01:06:47 -0400 Subject: [PATCH 024/112] Sort plugins in Lua API.rst --- docs/Lua API.rst | 422 +++++++++++++++++++++++------------------------ 1 file changed, 211 insertions(+), 211 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fb67610d1..2cf9e5db1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3697,6 +3697,88 @@ Native functions: ``start`` and ``end`` are tables containing positions (see ``xyz2pos``). ``name`` is used as the basis for the filename. +.. _building-hacks: + +building-hacks +============== + +This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although +plugin export a function it's recommended to use lua decorated function. + +.. contents:: + :local: + +Functions +--------- + +``registerBuilding(table)`` where table must contain name, as a workshop raw name, the rest are optional: + + :name: + custom workshop id e.g. ``SOAPMAKER`` + + .. note:: this is the only mandatory field. + + :fix_impassible: + if true make impassible tiles impassible to liquids too + :consume: + how much machine power is needed to work. + Disables reactions if not supplied enough and ``needs_power==1`` + :produce: + how much machine power is produced. + :needs_power: + if produced in network < consumed stop working, default true + :gears: + a table or ``{x=?,y=?}`` of connection points for machines. + :action: + a table of number (how much ticks to skip) and a function which + gets called on shop update + :animate: + a table of frames which can be a table of: + + a. tables of 4 numbers ``{tile,fore,back,bright}`` OR + b. empty table (tile not modified) OR + c. ``{x= y= + 4 numbers like in first case}``, + this generates full frame useful for animations that change little (1-2 tiles) + + :canBeRoomSubset: + a flag if this building can be counted in room. 1 means it can, 0 means it can't and -1 default building behaviour + :auto_gears: + a flag that automatically fills up gears and animate. It looks over building definition for gear icons and maps them. + + Animate table also might contain: + + :frameLength: + how many ticks does one frame take OR + :isMechanical: + a bool that says to try to match to mechanical system (i.e. how gears are turning) + +``getPower(building)`` returns two number - produced and consumed power if building can be modified and returns nothing otherwise + +``setPower(building,produced,consumed)`` sets current productiona and consumption for a building. + +Examples +-------- + +Simple mechanical workshop:: + + require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", + consume=15, + gears={x=0,y=0}, --connection point + animate={ + isMechanical=true, --animate the same conn. point as vanilla gear + frames={ + {{x=0,y=0,42,7,0,0}}, --first frame, 1 changed tile + {{x=0,y=0,15,7,0,0}} -- second frame, same + } + } + +Or with auto_gears:: + + require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", + consume=15, + auto_gears=true + } + buildingplan ============ @@ -3751,15 +3833,134 @@ Native functions: The lua module file also re-exports functions from ``dfhack.burrows``. -sort -==== +.. _cxxrandom: -Does not export any native functions as of now. Instead, it -calls lua code to perform the actual ordering of list items. +cxxrandom +========= + +Exposes some features of the C++11 random number library to Lua. + +.. contents:: + :local: + +Native functions (exported to Lua) +---------------------------------- + +- ``GenerateEngine(seed)`` + + returns engine id + +- ``DestroyEngine(rngID)`` + + destroys corresponding engine + +- ``NewSeed(rngID, seed)`` + + re-seeds engine + +- ``rollInt(rngID, min, max)`` + + generates random integer + +- ``rollDouble(rngID, min, max)`` + + generates random double + +- ``rollNormal(rngID, avg, stddev)`` + + generates random normal[gaus.] + +- ``rollBool(rngID, chance)`` + + generates random boolean + +- ``MakeNumSequence(start, end)`` + + returns sequence id + +- ``AddToSequence(seqID, num)`` + + adds a number to the sequence + +- ``ShuffleSequence(rngID, seqID)`` + + shuffles the number sequence + +- ``NextInSequence(seqID)`` + + returns the next number in sequence + + +Lua plugin functions +-------------------- + +- ``MakeNewEngine(seed)`` + + returns engine id + +Lua plugin classes +------------------ + +``crng`` +~~~~~~~~ + +- ``init(id, df, dist)``: constructor + + - ``id``: Reference ID of engine to use in RNGenerations + - ``df`` (optional): bool indicating whether to destroy the Engine when the crng object is garbage collected + - ``dist`` (optional): lua number distribution to use + +- ``changeSeed(seed)``: alters engine's seed value +- ``setNumDistrib(distrib)``: sets the number distribution crng object should use + + - ``distrib``: number distribution object to use in RNGenerations + +- ``next()``: returns the next number in the distribution +- ``shuffle()``: effectively shuffles the number distribution + +``normal_distribution`` +~~~~~~~~~~~~~~~~~~~~~~~ + +- ``init(avg, stddev)``: constructor +- ``next(id)``: returns next number in the distribution + + - ``id``: engine ID to pass to native function + +``real_distribution`` +~~~~~~~~~~~~~~~~~~~~~ + +- ``init(min, max)``: constructor +- ``next(id)``: returns next number in the distribution + + - ``id``: engine ID to pass to native function + +``int_distribution`` +~~~~~~~~~~~~~~~~~~~~ + +- ``init(min, max)``: constructor +- ``next(id)``: returns next number in the distribution + + - ``id``: engine ID to pass to native function + +``bool_distribution`` +~~~~~~~~~~~~~~~~~~~~~ + +- ``init(min, max)``: constructor +- ``next(id)``: returns next boolean in the distribution + + - ``id``: engine ID to pass to native function + +``num_sequence`` +~~~~~~~~~~~~~~~~ + +- ``init(a, b)``: constructor +- ``add(num)``: adds num to the end of the number sequence +- ``shuffle()``: shuffles the sequence of numbers +- ``next()``: returns next number in the sequence .. _eventful: -Eventful +eventful ======== This plugin exports some events to lua thus allowing to run lua functions @@ -3920,91 +4121,9 @@ Integrated tannery:: b=require "plugins.eventful" b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS") -.. _building-hacks: - -Building-hacks -============== - -This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although -plugin export a function it's recommended to use lua decorated function. - -.. contents:: - :local: - -Functions ---------- - -``registerBuilding(table)`` where table must contain name, as a workshop raw name, the rest are optional: - - :name: - custom workshop id e.g. ``SOAPMAKER`` - - .. note:: this is the only mandatory field. - - :fix_impassible: - if true make impassible tiles impassible to liquids too - :consume: - how much machine power is needed to work. - Disables reactions if not supplied enough and ``needs_power==1`` - :produce: - how much machine power is produced. - :needs_power: - if produced in network < consumed stop working, default true - :gears: - a table or ``{x=?,y=?}`` of connection points for machines. - :action: - a table of number (how much ticks to skip) and a function which - gets called on shop update - :animate: - a table of frames which can be a table of: - - a. tables of 4 numbers ``{tile,fore,back,bright}`` OR - b. empty table (tile not modified) OR - c. ``{x= y= + 4 numbers like in first case}``, - this generates full frame useful for animations that change little (1-2 tiles) - - :canBeRoomSubset: - a flag if this building can be counted in room. 1 means it can, 0 means it can't and -1 default building behaviour - :auto_gears: - a flag that automatically fills up gears and animate. It looks over building definition for gear icons and maps them. - - Animate table also might contain: - - :frameLength: - how many ticks does one frame take OR - :isMechanical: - a bool that says to try to match to mechanical system (i.e. how gears are turning) - -``getPower(building)`` returns two number - produced and consumed power if building can be modified and returns nothing otherwise - -``setPower(building,produced,consumed)`` sets current productiona and consumption for a building. - -Examples --------- - -Simple mechanical workshop:: - - require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", - consume=15, - gears={x=0,y=0}, --connection point - animate={ - isMechanical=true, --animate the same conn. point as vanilla gear - frames={ - {{x=0,y=0,42,7,0,0}}, --first frame, 1 changed tile - {{x=0,y=0,15,7,0,0}} -- second frame, same - } - } - -Or with auto_gears:: - - require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", - consume=15, - auto_gears=true - } - .. _luasocket: -Luasocket +luasocket ========= A way to access csocket from lua. The usage is made similar to luasocket in vanilla lua distributions. Currently @@ -4088,130 +4207,11 @@ Functions returns a table with w*h*4 entries of rendered tiles. The format is same as ``df.global.gps.screen`` (tile,foreground,bright,background). -.. _cxxrandom: - -cxxrandom -========= - -Exposes some features of the C++11 random number library to Lua. - -.. contents:: - :local: - -Native functions (exported to Lua) ----------------------------------- - -- ``GenerateEngine(seed)`` - - returns engine id - -- ``DestroyEngine(rngID)`` - - destroys corresponding engine - -- ``NewSeed(rngID, seed)`` - - re-seeds engine - -- ``rollInt(rngID, min, max)`` - - generates random integer - -- ``rollDouble(rngID, min, max)`` - - generates random double - -- ``rollNormal(rngID, avg, stddev)`` - - generates random normal[gaus.] - -- ``rollBool(rngID, chance)`` - - generates random boolean - -- ``MakeNumSequence(start, end)`` - - returns sequence id - -- ``AddToSequence(seqID, num)`` - - adds a number to the sequence - -- ``ShuffleSequence(rngID, seqID)`` - - shuffles the number sequence - -- ``NextInSequence(seqID)`` - - returns the next number in sequence - - -Lua plugin functions --------------------- - -- ``MakeNewEngine(seed)`` - - returns engine id - -Lua plugin classes ------------------- - -``crng`` -~~~~~~~~ - -- ``init(id, df, dist)``: constructor - - - ``id``: Reference ID of engine to use in RNGenerations - - ``df`` (optional): bool indicating whether to destroy the Engine when the crng object is garbage collected - - ``dist`` (optional): lua number distribution to use - -- ``changeSeed(seed)``: alters engine's seed value -- ``setNumDistrib(distrib)``: sets the number distribution crng object should use - - - ``distrib``: number distribution object to use in RNGenerations - -- ``next()``: returns the next number in the distribution -- ``shuffle()``: effectively shuffles the number distribution - -``normal_distribution`` -~~~~~~~~~~~~~~~~~~~~~~~ - -- ``init(avg, stddev)``: constructor -- ``next(id)``: returns next number in the distribution - - - ``id``: engine ID to pass to native function - -``real_distribution`` -~~~~~~~~~~~~~~~~~~~~~ - -- ``init(min, max)``: constructor -- ``next(id)``: returns next number in the distribution - - - ``id``: engine ID to pass to native function - -``int_distribution`` -~~~~~~~~~~~~~~~~~~~~ - -- ``init(min, max)``: constructor -- ``next(id)``: returns next number in the distribution - - - ``id``: engine ID to pass to native function - -``bool_distribution`` -~~~~~~~~~~~~~~~~~~~~~ - -- ``init(min, max)``: constructor -- ``next(id)``: returns next boolean in the distribution - - - ``id``: engine ID to pass to native function - -``num_sequence`` -~~~~~~~~~~~~~~~~ +sort +==== -- ``init(a, b)``: constructor -- ``add(num)``: adds num to the end of the number sequence -- ``shuffle()``: shuffles the sequence of numbers -- ``next()``: returns next number in the sequence +Does not export any native functions as of now. Instead, it +calls lua code to perform the actual ordering of list items. .. _xlsxreader: From 2bd0e938addbd1b59f7fbc90e98f448128da8f90 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 20 Oct 2020 01:11:01 -0400 Subject: [PATCH 025/112] Move pathable to Lua API docs --- docs/Lua API.rst | 13 +++++++++++++ docs/Plugins.rst | 18 +++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2cf9e5db1..ecc9ef88e 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4207,6 +4207,19 @@ Functions returns a table with w*h*4 entries of rendered tiles. The format is same as ``df.global.gps.screen`` (tile,foreground,bright,background). +.. _pathable: + +pathable +======== + +This plugin implements the back end of the `gui/pathable` script. It exports a +single Lua function, in ``hack/lua/plugins/pathable.lua``: + +* ``paintScreen(cursor[,skip_unrevealed])``: Paint each visible of the screen + green or red, depending on whether it can be pathed to from the tile at + ``cursor``. If ``skip_unrevealed`` is specified and true, do not draw + unrevealed tiles. + sort ==== diff --git a/docs/Plugins.rst b/docs/Plugins.rst index f9ce3184c..f96bd78de 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -117,19 +117,6 @@ A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map. -.. _pathable: - -pathable -======== - -This plugin implements the back end of the `gui/pathable` script. It exports a -single Lua function, in ``hack/lua/plugins/pathable.lua``: - -* ``paintScreen(cursor[,skip_unrevealed])``: Paint each visible of the screen - green or red, depending on whether it can be pathed to from the tile at - ``cursor``. If ``skip_unrevealed`` is specified and true, do not draw - unrevealed tiles. - .. _probe: probe @@ -2971,9 +2958,10 @@ Lua API Some plugins consist solely of native libraries exposed to Lua. They are listed in the `lua-api` file under `lua-plugins`: -* `eventful` * `building-hacks` +* `cxxrandom` +* `eventful` * `luasocket` * `map-render` -* `cxxrandom` +* `pathable` * `xlsxreader` From 26505acb70d0545487b8f536c1efd09dbf51d981 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 20 Oct 2020 01:18:25 -0400 Subject: [PATCH 026/112] Add some cross-references and clean up --- docs/Lua API.rst | 15 ++++++++------- docs/Plugins.rst | 11 +++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index ecc9ef88e..cbc97bb1d 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3687,7 +3687,7 @@ the plugin. See existing files in ``plugins/lua`` for examples. blueprint ========= -Native functions: +Native functions provided by the `blueprint` plugin: * ``dig(start, end, name)`` * ``build(start, end, name)`` @@ -3782,7 +3782,7 @@ Or with auto_gears:: buildingplan ============ -Native functions: +Native functions provided by the `buildingplan` plugin: * ``bool isPlannableBuilding(df::building_type type)`` returns whether the building type is handled by buildingplan * ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list @@ -3791,7 +3791,7 @@ Native functions: burrows ======= -Implements extended burrow manipulations. +The `burrows` plugin implements extended burrow manipulations. Events: @@ -4197,8 +4197,9 @@ A class with all the tcp functionality. map-render ========== -A way to ask df to render a slice of map. This uses native df rendering function so it's highly dependant on -df settings (e.g. used tileset, colors, if using graphics or not and so on...) +A way to ask DF to render a section of the fortress mode map. This uses a native +DF rendering function so it's highly dependent on DF settings (e.g. tileset, +colors, etc.) Functions --------- @@ -4223,8 +4224,8 @@ single Lua function, in ``hack/lua/plugins/pathable.lua``: sort ==== -Does not export any native functions as of now. Instead, it -calls lua code to perform the actual ordering of list items. +The `sort ` plugin does not export any native functions as of now. +Instead, it calls Lua code to perform the actual ordering of list items. .. _xlsxreader: diff --git a/docs/Plugins.rst b/docs/Plugins.rst index f96bd78de..eaecb1cba 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -37,7 +37,9 @@ For more information, see `the full Stonesense README `. blueprint ========= -Exports a portion of your fortress into QuickFort style blueprint files.:: +Exports a portion of your fortress into QuickFort style blueprint files. + +Usage:: blueprint [dig] [build] [place] [query] @@ -781,6 +783,7 @@ Adds a :kbd:`q` menu for track stops, which is completely blank by default. This allows you to view and/or change the track stop's friction and dump direction settings, using the keybindings from the track stop building interface. +.. _sort: .. _sort-items: sort-items @@ -1940,10 +1943,10 @@ Options: :L: Low Traffic :R: Restricted Traffic -.. _burrow: +.. _burrows: -burrow -====== +burrows +======= Miscellaneous burrow control. Allows manipulating burrows and automated burrow expansion while digging. From 00f012d334858e65e5e5da90a0da10d033cc64f6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 21 Oct 2020 17:52:23 -0400 Subject: [PATCH 027/112] GitHub Actions: add alternative plugin builds --- .github/workflows/build.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0164ff2e..7aacd6950 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: build: runs-on: ${{ matrix.os }} - name: build (Linux, GCC ${{ matrix.gcc }}) + name: build (Linux, GCC ${{ matrix.gcc }}, ${{ matrix.plugins }} plugins) strategy: fail-fast: false matrix: @@ -14,9 +14,18 @@ jobs: gcc: - 4.8 - 7 + plugins: + - supported include: - os: ubuntu-20.04 gcc: 10 + plugins: supported + - os: ubuntu-20.04 + gcc: 10 + plugins: dev + - os: ubuntu-20.04 + gcc: 10 + plugins: stonesense steps: - name: Set up Python 3 uses: actions/setup-python@v2 @@ -37,7 +46,6 @@ jobs: zlib1g-dev pip install sphinx - name: Install GCC - if: ${{ matrix.gcc < 7 || matrix.gcc > 9 }} run: | sudo apt-get install gcc-${{ matrix.gcc }} g++-${{ matrix.gcc }} - name: Clone DFHack @@ -70,11 +78,15 @@ jobs: -B build-ci \ -G Ninja \ -DDFHACK_BUILD_ARCH=64 \ - -DBUILD_DOCS:BOOL=ON \ -DBUILD_TESTS:BOOL=ON \ + -DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'dev' }} \ + -DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'dev' }} \ + -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'stonesense' }} \ + -DBUILD_SUPPORTED:BOOL=${{ matrix.plugins == 'supported' }} \ -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" ninja -C build-ci install - name: Run tests + if: ${{ matrix.plugins == 'supported' }} run: | export TERM=dumb mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init From 9357c9f8886b5585673cfbbb3d64c06d1870757e Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 21 Oct 2020 20:13:50 -0400 Subject: [PATCH 028/112] Only upload test artifacts if tests ran --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7aacd6950..16e3b6415 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,6 +86,7 @@ jobs: -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" ninja -C build-ci install - name: Run tests + id: run_tests if: ${{ matrix.plugins == 'supported' }} run: | export TERM=dumb @@ -96,7 +97,8 @@ jobs: cp "$DF_FOLDER/test_status.json" "$DF_FOLDER"/*.log artifacts - name: Upload test artifacts uses: actions/upload-artifact@v1 - if: success() || failure() + if: (success() || failure()) && steps.run_tests.outcome != 'skipped' + continue-on-error: true with: name: test-artifacts path: artifacts From a0ff7c393b6a34a020f203a75b8b9de955a6faa4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 21 Oct 2020 20:14:16 -0400 Subject: [PATCH 029/112] Give artifacts a unique name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 16e3b6415..f0bb79cda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,7 +100,7 @@ jobs: if: (success() || failure()) && steps.run_tests.outcome != 'skipped' continue-on-error: true with: - name: test-artifacts + name: test-artifacts-${{ matrix.gcc }} path: artifacts - name: Clean up DF folder # prevent DFHack-generated files from ending up in the cache From 7e78d8802eebfe3ec1d0d505106a1c96dacfc1dd Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 22 Oct 2020 21:37:49 -0700 Subject: [PATCH 030/112] migrate qf's buildings_use_blocks to buildingplan - remove buildings_use_blocks setting from quickfort config file - add a new Buildingplan Global Settings dialog to house global settings - move Quickfort Mode (for legacy Python Quickfort) into that dialog - add four settings to control how generic building materials are matched: - blocks - boulders - logs - bars - ajust the buildingplan algorithm to register duplicate tasks for building material item filters, one for each type. since we track how many items we've matched for a filter, the first matched item will "win" and the extras will get detected as invalid and popped off the queue. - ensure boulders, logs, and bars are scanned last, and in that order - more global settings planned for the future! see http://www.bay12forums.com/smf/index.php?topic=176889.msg8202679#msg8202679 --- dfhack-config/quickfort/quickfort.txt | 13 +- docs/changelog.txt | 6 +- plugins/buildingplan-planner.cpp | 282 ++++++++++++++++++-------- plugins/buildingplan-planner.h | 6 + plugins/buildingplan.cpp | 65 ++++-- plugins/lua/buildingplan.lua | 111 ++++++++++ 6 files changed, 377 insertions(+), 106 deletions(-) diff --git a/dfhack-config/quickfort/quickfort.txt b/dfhack-config/quickfort/quickfort.txt index c1d70201f..3a57bfbaf 100644 --- a/dfhack-config/quickfort/quickfort.txt +++ b/dfhack-config/quickfort/quickfort.txt @@ -5,19 +5,16 @@ # # If you have edited this file but want to revert to "factory defaults", delete # this file and a fresh one will be copied from -# dfhack-config/default/quickfort/qickfort.txt the next time you start DFHack. +# dfhack-config/default/quickfort/quickfort.txt the next time you start DFHack. # Directory tree to search for blueprints. Can be set to an absolute or relative # path. If set to a relative path, resolves to a directory under the DF folder. +# Note that if you change this directory, you will not automatically pick up +# blueprints written by the DFHack "blueprint" plugin (which always writes to +# the "blueprints" dir). blueprints_dir=blueprints -# Force all blueprint buildings that could be built with any building material -# to only use blocks. The prevents logs, boulders, and bars (e.g. potash and -# coal) from being wasted on constructions. If set to false, buildings will be -# built with any available building material. -buildings_use_blocks=true - -# Set to "true" or "false". If true, will designate dig blueprints in marker +# Set to "true" or "false". If true, will designate all dig blueprints in marker # mode. If false, only cells with dig codes explicitly prefixed with an "m" will # be designated in marker mode. force_marker_mode=false diff --git a/docs/changelog.txt b/docs/changelog.txt index dbe296c85..b26984d53 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,9 +38,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences ## Misc Improvements -- `buildingplan`: all buildings, furniture, and constructions are now supported (except for the few building types not supported by dfhack itself) -- `buildingplan`: now respects building job_item filters when matching items +- `buildingplan`: all buildings, furniture, and constructions are now supported (except for instruments) +- `buildingplan`: now respects building job_item filters when matching items, so you can set your own programmatic filters for buildings before submitting them to buildingplan - `buildingplan`: default filter setting for max quality changed from ``artifact`` to ``masterwork`` +- `buildingplan`: new global settings page accessible via ``G`` hotkey when on any building build screen; ``Quickfort Mode`` toggle for legacy Python Quickfort has been moved to this page +- `buildingplan`: new global settings for whether generic building materials should match blocks, boulders, logs, and/or bars. defaults are everything but bars. ## API - `buildingplan`: added Lua interface API diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 0a714854c..42c84f4dc 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -501,6 +501,33 @@ void migrateV1ToV2() } } +static void init_global_settings(std::map & settings) +{ + settings.clear(); + settings["blocks"] = true; + settings["boulders"] = true; + settings["logs"] = true; + settings["bars"] = false; +} + +const std::map & Planner::getGlobalSettings() const +{ + return global_settings; +} + +bool Planner::setGlobalSetting(std::string name, bool value) +{ + if (global_settings.count(name) == 0) + { + debug("attempted to set invalid setting: '%s'", name.c_str()); + return false; + } + debug("global setting '%s' %d -> %d", + name.c_str(), global_settings[name], value); + global_settings[name] = value; + return true; +} + void Planner::reset() { debug("resetting Planner state"); @@ -508,6 +535,8 @@ void Planner::reset() planned_buildings.clear(); tasks.clear(); + init_global_settings(global_settings); + migrateV1ToV2(); std::vector items; @@ -584,6 +613,41 @@ static std::string getBucket(const df::job_item & ji, return ser.str(); } +// get a list of item vectors that we should search for matches +static std::vector getVectorIds(df::job_item *job_item, + const std::map & global_settings) +{ + std::vector ret; + + // if the filter already has the vector_id set to something specific, use it + if (job_item->vector_id > df::job_item_vector_id::IN_PLAY) + { + debug("using vector_id from job_item: %s", + ENUM_KEY_STR(job_item_vector_id, job_item->vector_id).c_str()); + ret.push_back(job_item->vector_id); + return ret; + } + + // if the filer is for building material, refer to our global settings for + // which vectors to search + if (job_item->flags2.bits.building_material) + { + if (global_settings.at("blocks")) + ret.push_back(df::job_item_vector_id::BLOCKS); + if (global_settings.at("boulders")) + ret.push_back(df::job_item_vector_id::BOULDER); + if (global_settings.at("logs")) + ret.push_back(df::job_item_vector_id::WOOD); + if (global_settings.at("bars")) + ret.push_back(df::job_item_vector_id::BAR); + } + + // fall back to IN_PLAY if no other vector was appropriate + if (ret.empty()) + ret.push_back(df::job_item_vector_id::IN_PLAY); + return ret; +} + bool Planner::registerTasks(PlannedBuilding & pb) { df::building * bld = pb.getBuilding(); @@ -599,22 +663,27 @@ bool Planner::registerTasks(PlannedBuilding & pb) debug("unexpected number of job items: want >0, got %d", num_job_items); return false; } + int32_t id = bld->id; for (int job_item_idx = 0; job_item_idx < num_job_items; ++job_item_idx) { - auto vector_id = df::job_item_vector_id::IN_PLAY; auto job_item = job_items[job_item_idx]; - if (job_item->vector_id) - vector_id = job_item->vector_id; auto bucket = getBucket(*job_item, pb.getFilters()); - for (int item_num = 0; item_num < job_item->quantity; ++item_num) + auto vector_ids = getVectorIds(job_item, global_settings); + + // if there are multiple vector_ids, schedule duplicate tasks. after + // the correct number of items are matched, the extras will get popped + // as invalid + for (auto vector_id : vector_ids) { - int32_t id = bld->id; - tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx)); - debug("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(), - bucket.c_str(), id, job_item_idx, tasks.size(), - tasks[vector_id].size(), tasks[vector_id][bucket].size()); + for (int item_num = 0; item_num < job_item->quantity; ++item_num) + { + tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx)); + debug("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(), + bucket.c_str(), id, job_item_idx, tasks.size(), + tasks[vector_id].size(), tasks[vector_id][bucket].size()); + } } } return true; @@ -794,99 +863,144 @@ void Planner::popInvalidTasks(std::queue> & task_queue) } } -void Planner::doCycle() +void Planner::doVector(df::job_item_vector_id vector_id, + std::map>> & buckets) { - debug("running cycle for %zu registered buildings", - planned_buildings.size()); - for (auto it = tasks.begin(); it != tasks.end();) + auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); + auto item_vector = df::global::world->items.other[other_id]; + debug("matching %zu item(s) in vector %s against %zu filter bucket(s)", + item_vector.size(), + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + buckets.size()); + for (auto item_it = item_vector.rbegin(); + item_it != item_vector.rend(); + ++item_it) { - auto & buckets = it->second; - auto other_id = ENUM_ATTR(job_item_vector_id, other, it->first); - auto item_vector = df::global::world->items.other[other_id]; - debug("matching %zu item(s) in vector %s against %zu filter bucket(s)", - item_vector.size(), - ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), - buckets.size()); - for (auto item_it = item_vector.rbegin(); - item_it != item_vector.rend(); - ++item_it) + auto item = *item_it; + if (!itemPassesScreen(item)) + continue; + for (auto bucket_it = buckets.begin(); bucket_it != buckets.end();) { - auto item = *item_it; - if (!itemPassesScreen(item)) + auto & task_queue = bucket_it->second; + popInvalidTasks(task_queue); + if (task_queue.empty()) + { + debug("removing empty bucket: %s/%s; %zu bucket(s) left", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket_it->first.c_str(), + buckets.size() - 1); + bucket_it = buckets.erase(bucket_it); continue; - for (auto bucket_it = buckets.begin(); bucket_it != buckets.end();) + } + auto & task = task_queue.front(); + auto id = task.first; + auto & pb = planned_buildings.at(id); + auto building = pb.getBuilding(); + auto job = building->jobs[0]; + auto filter_idx = task.second; + if (matchesFilters(item, job->job_items[filter_idx], + pb.getFilters()[filter_idx]) + && DFHack::Job::attachJobItem(job, item, + df::job_item_ref::Hauled, filter_idx)) { - auto & task_queue = bucket_it->second; - popInvalidTasks(task_queue); - if (task_queue.empty()) + MaterialInfo material; + material.decode(item); + ItemTypeInfo item_type; + item_type.decode(item); + debug("attached %s %s to filter %d for %s(%d): %s/%s", + material.toString().c_str(), + item_type.toString().c_str(), + filter_idx, + ENUM_KEY_STR(building_type, building->getType()).c_str(), + id, + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket_it->first.c_str()); + // keep quantity aligned with the actual number of remaining + // 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(); + if (isJobReady(job)) { - debug("removing empty bucket: %s/%s; %zu bucket(s) left", - ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), - bucket_it->first.c_str(), - buckets.size() - 1); - bucket_it = buckets.erase(bucket_it); - continue; + finalizeBuilding(building); + unregisterBuilding(id); } - auto & task = task_queue.front(); - auto id = task.first; - auto & pb = planned_buildings.at(id); - auto building = pb.getBuilding(); - auto job = building->jobs[0]; - auto filter_idx = task.second; - if (matchesFilters(item, job->job_items[filter_idx], - pb.getFilters()[filter_idx]) - && DFHack::Job::attachJobItem(job, item, - df::job_item_ref::Hauled, filter_idx)) + if (task_queue.empty()) { - MaterialInfo material; - material.decode(item); - ItemTypeInfo item_type; - item_type.decode(item); - debug("attached %s %s to filter %d for %s(%d): %s/%s", - material.toString().c_str(), - item_type.toString().c_str(), - filter_idx, - ENUM_KEY_STR(building_type, building->getType()).c_str(), - id, - ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), - bucket_it->first.c_str()); - // keep quantity aligned with the actual number of remaining - // 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(); - if (isJobReady(job)) - { - finalizeBuilding(building); - unregisterBuilding(id); - } - if (task_queue.empty()) - { - debug( - "removing empty item bucket: %s/%s; %zu left", - ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), - bucket_it->first.c_str(), - buckets.size() - 1); - buckets.erase(bucket_it); - } - // we found a home for this item; no need to look further - break; + debug( + "removing empty item bucket: %s/%s; %zu left", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + bucket_it->first.c_str(), + buckets.size() - 1); + buckets.erase(bucket_it); } - ++bucket_it; - } - if (buckets.empty()) + // we found a home for this item; no need to look further break; + } + ++bucket_it; } + if (buckets.empty()) + break; + } +} + +struct VectorsToScanLast +{ + std::vector vectors; + VectorsToScanLast() + { + // order is important here. we want to match boulders before wood and + // everything before bars. blocks are not listed here since we'll have + // already scanned them when we did the first pass through the buckets. + vectors.push_back(df::job_item_vector_id::BOULDER); + vectors.push_back(df::job_item_vector_id::WOOD); + vectors.push_back(df::job_item_vector_id::BAR); + } +}; + +void Planner::doCycle() +{ + debug("running cycle for %zu registered building(s)", + planned_buildings.size()); + static const VectorsToScanLast vectors_to_scan_last; + for (auto it = tasks.begin(); it != tasks.end();) + { + auto vector_id = it->first; + // we could make this a set, but it's only three elements + if (std::find(vectors_to_scan_last.vectors.begin(), + vectors_to_scan_last.vectors.end(), + vector_id) != vectors_to_scan_last.vectors.end()) + { + ++it; + continue; + } + + auto & buckets = it->second; + doVector(vector_id, buckets); if (buckets.empty()) { debug("removing empty vector: %s; %zu vector(s) left", - ENUM_KEY_STR(job_item_vector_id, it->first).c_str(), + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), tasks.size() - 1); it = tasks.erase(it); } else ++it; } + for (auto vector_id : vectors_to_scan_last.vectors) + { + if (tasks.count(vector_id) == 0) + continue; + auto & buckets = tasks[vector_id]; + doVector(vector_id, buckets); + if (buckets.empty()) + { + debug("removing empty vector: %s; %zu vector(s) left", + ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), + tasks.size() - 1); + tasks.erase(vector_id); + } + } debug("cycle done; %zu registered building(s) left", planned_buildings.size()); } diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index 22d0487c3..18cfaf0b1 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -104,6 +104,9 @@ public: std::vector &item_filters; }; + const std::map & getGlobalSettings() const; + bool setGlobalSetting(std::string name, bool value); + void reset(); void addPlannedBuilding(df::building *bld); @@ -117,6 +120,7 @@ public: void doCycle(); private: + std::map global_settings; std::unordered_map, BuildingTypeKeyHash> default_item_filters; @@ -128,6 +132,8 @@ private: bool registerTasks(PlannedBuilding &plannedBuilding); void unregisterBuilding(int32_t id); void popInvalidTasks(std::queue> &task_queue); + void doVector(df::job_item_vector_id vector_id, + std::map>> & buckets); }; extern Planner planner; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 58c911dcf..52bd5118f 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -18,7 +18,7 @@ DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 2.0 REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); -REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(world); // used in buildingplan library #define MAX_MASK 10 #define MAX_MATERIAL 21 @@ -287,9 +287,9 @@ static bool is_planmode_enabled(BuildingTypeKey key) static std::string get_item_label(const BuildingTypeKey &key, int item_idx) { - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); - Lua::StackUnwinder top(L); + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); if (!lua_checkstack(L, 5) || !Lua::PushModulePublic( @@ -314,11 +314,11 @@ static std::string get_item_label(const BuildingTypeKey &key, int item_idx) static bool construct_planned_building() { - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); - CoreSuspendClaimer suspend; - Lua::StackUnwinder top(L); + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); if (!(lua_checkstack(L, 1) && Lua::PushModulePublic(out, L, "plugins.buildingplan", @@ -339,6 +339,36 @@ static bool construct_planned_building() return true; } +static void show_global_settings_dialog() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "show_global_settings_dialog")) + { + debug("Failed to push the module"); + return; + } + + lua_newtable(L); + int ctable = lua_gettop(L); + Lua::SetField(L, quickfort_mode, ctable, "quickfort_mode"); + + for (auto & setting : planner.getGlobalSettings()) + { + Lua::SetField(L, setting.second, ctable, setting.first.c_str()); + } + + if (!Lua::SafeCall(out, L, 1, 0)) + { + debug("Failed call to show_global_settings_dialog"); + return; + } +} + struct buildingplan_query_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -522,7 +552,7 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest } if (input->count(interface_key::CUSTOM_P) || - input->count(interface_key::CUSTOM_F) || + input->count(interface_key::CUSTOM_G) || input->count(interface_key::CUSTOM_D) || input->count(interface_key::CUSTOM_M)) { @@ -536,9 +566,9 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest Gui::refreshSidebar(); return true; } - if (input->count(interface_key::CUSTOM_SHIFT_F)) + if (input->count(interface_key::CUSTOM_SHIFT_G)) { - quickfort_mode = !quickfort_mode; + show_global_settings_dialog(); return true; } @@ -648,7 +678,7 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest } OutputToggleString(x, y, "Planning Mode", "P", planmode_enabled[key], true, left_margin); - OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); + OutputHotkeyString(x, y, "Global Settings", "G", true, left_margin); if (!is_planmode_enabled(key)) return; @@ -893,10 +923,21 @@ static void scheduleCycle() { cycle_requested = true; } +static void setSetting(std::string name, bool value) { + if (name == "quickfort_mode") + { + debug("setting quickfort_mode %d -> %d", quickfort_mode, value); + quickfort_mode = value; + return; + } + planner.setGlobalSetting(name, value); +} + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isPlannableBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding), DFHACK_LUA_FUNCTION(doCycle), DFHACK_LUA_FUNCTION(scheduleCycle), + DFHACK_LUA_FUNCTION(setSetting), DFHACK_LUA_END }; diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 2de7db7d2..ac92082f3 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -4,6 +4,7 @@ local _ENV = mkmodule('plugins.buildingplan') Native functions: + * void setSetting(string name, boolean value) * bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) * void addPlannedBuilding(df::building *bld) * void doCycle() @@ -11,6 +12,7 @@ local _ENV = mkmodule('plugins.buildingplan') --]] +local dialogs = require('gui.dialogs') local guidm = require('gui.dwarfmode') require('dfhack.buildings') @@ -92,4 +94,113 @@ function construct_building_from_ui_state() return bld end +-- +-- GlobalSettings dialog +-- + +local GlobalSettings = defclass(GlobalSettings, dialogs.MessageBox) +GlobalSettings.focus_path = 'buildingplan_globalsettings' + +GlobalSettings.ATTRS{ + settings = {} +} + +function GlobalSettings:onDismiss() + for k,v in pairs(self.settings) do + -- call back into C++ to save changes + setSetting(k, v) + end +end + +-- does not need the core suspended. +function show_global_settings_dialog(settings) + GlobalSettings{ + frame_title="Buildingplan Global Settings", + settings=settings, + }:show() +end + +function GlobalSettings:toggle_setting(name) + self.settings[name] = not self.settings[name] +end + +function GlobalSettings:get_setting_string(name) + if self.settings[name] then return 'On' end + return 'Off' +end + +function GlobalSettings:is_setting_enabled(name) + return self.settings[name] +end + +function GlobalSettings:make_setting_label_token(text, key, name, width) + return {text=text, key=key, key_sep=': ', key_pen=COLOR_GREEN, + on_activate=self:callback('toggle_setting', name), width=width} +end + +function GlobalSettings:make_setting_value_token(name) + return {text=self:callback('get_setting_string', name), + enabled=self:callback('is_setting_enabled', name), + pen=COLOR_YELLOW, dpen=COLOR_GRAY} +end + +-- mockup: +--[[ + Buildingplan Global Settings + + e: Enable all: Off + Enables buildingplan for all building types. Use this to avoid having to + manually enable buildingplan for each building type that you want to plan. + Note that DFHack quickfort will use buildingplan to manage buildings + regardless of whether buildingplan is "enabled" for the building type. + + Allowed types for generic, fire-safe, and magma-safe building material: + b: Blocks: On + s: Boulders: On + w: Wood: On + r: Bars: Off + Changes to these settings will be applied to newly-planned buildings. + + A: Apply building material filter settings to existing planned buildings + Use this if your planned buildings can't be completed because the settings + above were too restrictive when the buildings were originally planned. + + M: Edit list of materials to avoid + potash + pearlash + ash + coal + Buildingplan will avoid using these material types when a planned building's + material filter is set to 'any'. They can stil be matched when they are + explicitly allowed by a planned building's material filter. Changes to this + list take effect for existing buildings immediately. + + g: Allow bags: Off + This allows bags to be placed where a 'coffer' is planned. + + f: Legacy Quickfort Mode: Off + Compatibility mode for the legacy Python-based Quickfort application. This + setting is not needed for DFHack quickfort. +--]] +function GlobalSettings:init() + self.subviews.label:setText{ + 'Allowed types for generic, fire-safe, and magma-safe building material:\n', + self:make_setting_label_token('Blocks', 'CUSTOM_B', 'blocks', 10), + self:make_setting_value_token('blocks'), '\n', + self:make_setting_label_token('Boulders', 'CUSTOM_S', 'boulders', 10), + self:make_setting_value_token('boulders'), '\n', + self:make_setting_label_token('Wood', 'CUSTOM_W', 'logs', 10), + self:make_setting_value_token('logs'), '\n', + self:make_setting_label_token('Bars', 'CUSTOM_R', 'bars', 10), + self:make_setting_value_token('bars'), '\n', + ' Changes to these settings will be applied to newly-planned buildings.\n', + '\n', + self:make_setting_label_token('Legacy Quickfort Mode', 'CUSTOM_F', + 'quickfort_mode', 23), + self:make_setting_value_token('quickfort_mode'), '\n', + ' Compatibility mode for the legacy Python-based Quickfort application.\n', + ' This setting is not needed for DFHack quickfort.' + } +end + return _ENV From 0f517f38f84ddd7a99ed50ffe776e2da20394885 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 22 Oct 2020 22:01:45 -0700 Subject: [PATCH 031/112] don't conflict with automaterial plugin shift buildingplan text down so we don't overwrite each other --- plugins/buildingplan.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 58c911dcf..910e5ebe4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -641,6 +641,12 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest int y = 23; + if (ui_build_selector->building_type == df::building_type::Construction) + { + // try not to conflict with the automaterial plugin UI + y = 34; + } + if (show_help) { OutputString(COLOR_BROWN, x, y, "Note: "); From 186f28a94b381da1b094e62134d46901e3d52f3a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 13 Oct 2020 22:44:16 -0700 Subject: [PATCH 032/112] filters for altars, display cases, and bookcases --- library/lua/dfhack/buildings.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua index 54f6fa19c..5bb84b1d1 100644 --- a/library/lua/dfhack/buildings.lua +++ b/library/lua/dfhack/buildings.lua @@ -179,6 +179,9 @@ local building_inputs = { [df.building_type.Slab] = { { item_type=df.item_type.SLAB } }, [df.building_type.NestBox] = { { has_tool_use=df.tool_uses.NEST_BOX, item_type=df.item_type.TOOL } }, [df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } }, + [df.building_type.OfferingPlace] = { { has_tool_use=df.tool_uses.PLACE_OFFERING, item_type=df.item_type.TOOL } }, + [df.building_type.Bookcase] = { { has_tool_use=df.tool_uses.BOOKCASE, item_type=df.item_type.TOOL } }, + [df.building_type.DisplayFurniture] = { { has_tool_use=df.tool_uses.DISPLAY_OBJECT, item_type=df.item_type.TOOL } }, [df.building_type.Rollers] = { { name='mechanism', From bc4dd7b1a354df4e587e30655989eed6c37ab887 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 23 Oct 2020 09:41:56 -0700 Subject: [PATCH 033/112] add walkthroughs for each dreamfort level --- data/blueprints/library/dreamfort.csv | 112 +++++++++++++++++++++----- 1 file changed, 92 insertions(+), 20 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 64d32ce8d..5a5c87dfc 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -1,26 +1,28 @@ #notes label(help) run me for the dreamfort walkthrough Welcome to the Dreamfort Walkthrough! -It can be difficult applying a set of blueprints that you did not write yourself. This walkthrough will guide you through the high-level steps of building Dreamfort. +It can be difficult applying a set of blueprints that you did not write yourself. This walkthrough will guide you through the high-level steps of building Dreamfort. Each level also has its own mini-walkthrough with specific steps for that level. "" -"The final fort will have a walled-in area on the surface for livestock, trading, and military. Be sure to bring some blocks with you for the initial set of workshops! One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall and dormitory for use until you get the services and beds levels set up." +"The final fort will have a walled-in area on the surface for livestock, trading, and military. One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall and dormitory for use until you get the services and housing levels set up." "" "Beyond those two, the other layers can be built in any order, at any z-level, according to your preference and the layout peculiarities of your embark site:" -"- The industry level has a compact, but complete set of workshops and stockpiles (minus what is already provided on the farming level)" +"- The industry level has a compact, but complete set of workshops and stockpiles (minus what is already provided on the farming level)." "- The services level has dining, hospital, and justice services, including a well system. This level is 4 z-levels deep." -"- The guildhall level has many empty rooms for building libraries, temples, and guildhalls" -- The suites level has fancy rooms for your nobles -- The apartments level(s) has small but well-furnished bedrooms for your other dwarves +"- The guildhall level has many empty rooms for building libraries, temples, and guildhalls." +- The suites level has fancy rooms for your nobles. +- The apartments level(s) has small but well-furnished bedrooms for your other dwarves. "" -"Run the ""help"" blueprints for each level for more details." +"Run each level's ""help"" blueprint (e.g. /surface_help) for more details." "" -"Dreamfort has a central staircase-based design. Choose a tile for your staircase on the surface in a nice, flat area. For all blueprints, the cursor will start on this tile on the z-level where you want to apply the blueprint." +"Dreamfort has a central staircase-based design. For all Dreamfort levels, place the cursor on the center staircase tile when you apply the blueprints. The first surface blueprint will designate a column of staircases that you can use as a guide." "" -"Blueprints that require manual steps (like 'assign minecart to hauling route') will leave a message telling you so after you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" on specific blueprints to start manufacturing needed items." +"Dreamfort blueprints take care of everything to get the fort up and running. You don't need to clear any trees or create any extra buildings or stockpiles (though of course you are free to do so). Blueprints that do require manual steps (like 'assign minecart to hauling route') will leave a message telling you so when you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" to start manufacturing needed items." "" -"Directly after embark, apply /surface1 on a flat area on the surface and /industry1 somewhere underground (but not immediately below the surface -- that will be for the farming level). Work your way through the steps for those levels: apply /surface2 when /surface1 is done, /industry2 when /industry1 is done, etc. Once you channel out parts of the surface with /surface3, you can start the farming sequence with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." +"Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should also avoid aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and about 10 blocks (of any material) to speed up construction of starting workshops." +"" +"Directly after embark, apply /surface1 on the surface (see /surface_help for how to select a good spot) and /industry1 at least two z-levels underground (the z-level immediately below the surface is reserved for the farming level). The walkthroughs for those levels will guide you. Once you channel out parts of the surface with /surface3, you can start the farming sequence on the z-level below the surface with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." "" "This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, it will be easier to look at the online spreadsheets than this giant .csv. You can view them at: https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP" -You are welcome to copy those files and make your own modifications! +You are welcome to copy those spreadsheets and make your own modifications! "#dreamfort.csv is generated with the following command: for fname in dreamfort*.xlsx; do xlsx2csv -a -p '' $fname; done | sed 's/,*$//'" #notes label(surface_help) @@ -28,7 +30,8 @@ You are welcome to copy those files and make your own modifications! "" Features: A starting set of workshops and stockpiles (which you can later remove once you establish your permanent workshops and storage) -Walls and lever-controlled gates for security +Trade depot area +"Walls, roof, and lever-controlled gates for security" Trap-filled hallways for invaders Livestock grazing area with nestbox zones and beehives A grid of 1x1 farm plots (meant to be managed with DFHack autofarm) @@ -36,20 +39,29 @@ An extra room near the entrance for a barracks (once you get your military going Miasma vents for the farming level that is intended to be built in the layer directly beneath the surface "" Manual steps you have to take: -Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox will manage the nestbox zones) +Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox can manage the nestbox zones) Connect levers to the gates "" "All blueprints are managed by the meta blueprints in the ""meta"" sheet. Each stage is meant to be applied after the previous stage is completely finished. For example, you can't build furniture until all the flooring has been constructed." "" Be sure to choose an embark site that has an area flat enough to use these blueprints! +"" +Surface Walkthrough: +"1) Choose a tile for your central staircase. The terrain around that tile should be perfectly flat. Trees are ok, but no slopes, rivers, or lakes. To be sure that the tile you've chosen is in a good spot, set the cursor over that tile and run ""quickfort run library/dreamfort.csv -n /surface4"". This will show you the eventual boundaries of the fort. Some wall segments might be missing due to existing trees, but that's ok. Make sure the area within the exterior walls is flat. Run ""quickfort undo library/dreamfort.csv -n /surface4"" to clean up." +"2) With the cursor on the chosen tile, run /surface1 to clear surrounding trees and set up your pastures. Remember to assign your dogs to the pasture around the staircase and your grazing animals to the large pasture. You can let your cats roam free." +"3) Once the trees have been cleared, run /surface2 to set up starting workshops/stockpiles and clear trees from a larger area. Run ""quickfort orders"" for /surface4, /surface5, and /surface6 to get a head start manufacturing items for those blueprints. If you want a consistent color for your walls and floors, remember to set the rock material in the buildingplan UI and in the manager orders for the blocks." +"4) Once the trees have been cleared from the larger area, run /surface3 to channel out the miasma vents for the farming level." +"5) When the channels have been dug, run /surface4 to build the protective walls and flooring. You can also start digging out the sub-surface farming level (/farming1) at this point. Although the vents will be covered with flooring, they will still work to prevent miasma." +"6) Once all the constructions are built, run /surface5 build gates, furniture, the trade depot, and traps." +"7) When at least the beehives are in place, run /surface6 to configure the hives and construct a roof." "#meta label(surface1) start(staircase center) message(This would be a good time to start digging the industry level. -Once the area is clear, continue with /surface2.) clear an area and set up pastures" +Once the area is clear of trees, continue with /surface2.) clear trees and set up pastures" clear_small/surface_clear_small zones/surface_zones "" "#meta label(surface2) start(staircase center) message(This would be a good time to queue manager orders for /surface4, /surface5, and /surface6. If you want a consistent color for your walls, remember to set the rock material in the buildingplan UI and in the manager orders for blocks. -Once the whole area is clear, continue with /surface3.) set up starting workshops/stockpiles and clear a larger area. if you didn't bring blocks, temporarily turn off the buildings_use_blocks setting so you can use wood or boulders" +Once the whole area is clear of trees, continue with /surface3.) set up starting workshops/stockpiles and clear a larger area" build_start/surface_build_start place_start/surface_place_start query_start/surface_query_start @@ -121,7 +133,34 @@ build_roof/surface_roof ,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,` - +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i #zone label(surface_zones) start(23; 25) hidden() message(remember to assign your dogs to the staircase pasture and your grazing animals to the large pasture) use the meta blueprints for normal application @@ -847,6 +886,12 @@ Manual steps you have to take: Assign the office to your manager Assign a minecart to your quantum garbage stockpile hauling route "If the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level" +"" +Farming Walkthough: +"1) Wait until you have completed /surface3 before digging out the farming level on the z-level below the surface, otherwise you will likely get caveins as your miners channel out the miasma vents over empty space." +"2) Start digging with /farming1 and get started on manufacturing furniture by running ""quickfort orders"" on /farming2." +"3) Once the level is dug out, run /farming2 to build workshops and build and configure stockpiles. Remember to assign a minecart to the newly-designated quantum garbage dump. There are also jugs, pots, and bags stockpiles on this level that should be configured to ""take"" from the industry level stockpiles once we get the industry level built." +"4) When the furniture is in place, run /farming3 to designate your temporary dining room and dormitory. The blueprint also attempts to assign the office to your manager, but double-check this assignment in case your dwarves are in an unexpected order." "#dig label(farming1) start(23; 25; staircase center) message(This would be a good time to queue up manager orders for /farming2. Once the area is dug out, continue with /farming2.)" @@ -1154,6 +1199,12 @@ 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" "If desired, set the stockpiles in the bottom right to auto-melt. This results in melting all weapons and armor that are not masterwork steel. This is great for your military, but it takes a *lot* of fuel. If you enable this, be sure you're in a heavily forested area, enable auto-chop, and set up manager orders to keep your coal stocks high." +"" +Industry Walkthrough: +1) You can start digging out /industry1 immediately after embark. It is best to choose a stone layer at least two layers underground so the boulders can be used by your starting workshops to manufacture needed items. +"2) Queue up manufacturing by running ""quickfort orders"" on /industry2." +"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 to the jugs, pots, and bags stockpiles on the farming level." +"4) If you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt." "#dig label(industry1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /industry2. Once the area is dug out, continue with /industry2.)" @@ -1309,8 +1360,8 @@ query/industry_query "Sets up public services (dining, hospital, etc.)" "" Features: -Spacious dining room (can be declared a tavern) -Food and drink stockpiles +Spacious dining room (also usable as a tavern) +Prepared food and drink stockpiles Well system (bring your own water) Public baths with soap stockpiles Three well-appointed jail cells @@ -1321,7 +1372,14 @@ Manual steps you have to take: "If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." "Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)" "Activate the bath pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." -"Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." +"Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water to prevent muddiness. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." +"" +Services Walkthough: +1) Start this level before your fort grows beyond around 50 dwarves so everyone has a place to eat. +"2) Start digging with /services1. Note that this digs out the main level and three levels below for the well plumbing. Start manufacturing with ""quickfort orders"" on /services2." +"3) Once the area is dug out, set up the furniture, stockpiles, and hospital and garbage dump zones with /services2. Fill your soap stockpiles around the bath channels by configuring them to take from the bar quantum stockpile (the one on the right near the forge) on the industry level." +"4) When all furniture is placed, run /services3 to configure your dining room and jail." +5) Fill the bath and wells with either a bucket brigade or by carefully routing flowing water to them. "#dig label(services1) start(23; 22; staircase center) message(This would be a good time to queue manager orders for /services2. Once the area is dug out, continue with /services2.)" @@ -1639,7 +1697,12 @@ query_stockpiles/services_query_stockpiles "Multiple 7x7 rooms for guildhalls, temples, libraries, etc." "" Features: -"Big empty rooms. Double-thick walls to ensure engravings are on the ""correct"" side. Fill with furniture and assign as needed." +"Big empty rooms. Double-thick walls to ensure engravings add value to the ""correct"" side. Fill with furniture and assign as needed." +"" +Guildhall Walkthrough: +"1) Dig out the rooms with /guildhall1 and queue up manufacturing by running ""quickfort orders"" on /guildhall2." +"2) Once the area is dug out, add in generic furniture with /guildhall2." +"3) Furnish individual rooms as you need specific guildhalls, libraries, and temples." "#dig label(guildhall1) start(25; 25; staircase center) message(This would be a good time to queue manager orders for /guildhall2. Once the area is dug out, continue with /guildhall2.)" @@ -1747,6 +1810,15 @@ Features: Well-appointed suites to satisfy most nobles Apartments with beds and storage to keep dwarves happy Meta blueprint included for designating 6 levels of apartments for a full 200+ dwarves +"" +Suites Walkthrough: +"1) Dig out the suites layer with /suites1 and queue up manufacturing by running ""quickfort orders"" on /suites2." +"2) Once the area is dug out, furnish the suites with /suites2. The rooms are left unconfigured so you can set them up as needed room types and assign them to specific nobles. Each room can serve as a bedroom, a dining hall, an office, or a tomb." +"" +Apartments Walkthrough: +"1) Dig out one layer of apartments with /apartments1, or 6 layers at once (enough for 200 dwarves) with /apartments1_stack. Run ""quickfort orders"" for /apartments2 once for every apartments layer that you are digging." +"2) Once a layer is dug out, furnish it with /apartments2." +"3) Once the beds are in place (the other furniture can still be unbuilt), configure the rooms with /apartments3. Once the urns are all in place, run ""burial -pets"" to set them all to accept burials." "#dig label(suites1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /suites2. Once the area is dug out, continue with /suites2) noble suites" ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d From 233ce26ab576ebc4b2a1d96514b7ee2c6a5047d5 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 23 Oct 2020 09:45:41 -0700 Subject: [PATCH 034/112] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index dbe296c85..764d4cd46 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,6 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `buildingplan`: all buildings, furniture, and constructions are now supported (except for the few building types not supported by dfhack itself) - `buildingplan`: now respects building job_item filters when matching items - `buildingplan`: default filter setting for max quality changed from ``artifact`` to ``masterwork`` +- The Dreamfort sample blueprints now have complete walkthroughs for each fort level ## API - `buildingplan`: added Lua interface API From 55a52554ee901e410eca840aaa5658cf57bd63bd Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 23 Oct 2020 13:55:14 -0700 Subject: [PATCH 035/112] use CheckDFObject instead of static cast Co-authored-by: Alan --- plugins/buildingplan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 703e567b7..82551be23 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -303,7 +303,7 @@ static bool construct_planned_building() return false; } - auto bld = static_cast(LuaWrapper::get_object_ref(L, -1)); + auto bld = Lua::CheckDFObject(L, -1); lua_pop(L, 1); if (!bld) From e614d16e0e99b9ce2294ce49d1fd7db1e7f8fd57 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 23 Oct 2020 15:30:40 -0700 Subject: [PATCH 036/112] use Lua::CheckDFObject instead of static cast --- plugins/fortplan.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 8fbf8fb72..07685ca2b 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -72,8 +72,7 @@ struct BuildingInfo { if (!Lua::SafeCall(out, L, 4, 1)) return false; - auto bld = - static_cast(LuaWrapper::get_object_ref(L, -1)); + auto bld = Lua::CheckDFObject(L, -1); lua_pop(L, 1); if (!bld) From a3bec346a7dc78a32a5c3977595f9d8a13175b56 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 23 Oct 2020 20:25:56 -0700 Subject: [PATCH 037/112] clarify text a bit and move stair guide --- data/blueprints/library/dreamfort.csv | 67 ++++++++++++++------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 5a5c87dfc..914562093 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -15,11 +15,11 @@ It can be difficult applying a set of blueprints that you did not write yourself "" "Dreamfort has a central staircase-based design. For all Dreamfort levels, place the cursor on the center staircase tile when you apply the blueprints. The first surface blueprint will designate a column of staircases that you can use as a guide." "" -"Dreamfort blueprints take care of everything to get the fort up and running. You don't need to clear any trees or create any extra buildings or stockpiles (though of course you are free to do so). Blueprints that do require manual steps (like 'assign minecart to hauling route') will leave a message telling you so when you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" to start manufacturing needed items." +"Dreamfort blueprints take care of everything to get the fort up and running. You don't need to clear any trees or create any extra buildings or stockpiles (though of course you are free to do so). Blueprints that do require manual steps (like 'assign minecart to hauling route') will leave a message telling you so when you run them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" to start manufacturing needed items." "" "Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should also avoid aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and about 10 blocks (of any material) to speed up construction of starting workshops." "" -"Directly after embark, apply /surface1 on the surface (see /surface_help for how to select a good spot) and /industry1 at least two z-levels underground (the z-level immediately below the surface is reserved for the farming level). The walkthroughs for those levels will guide you. Once you channel out parts of the surface with /surface3, you can start the farming sequence on the z-level below the surface with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." +"Directly after embark, apply /surface1 on the surface (see /surface_help for how to select a good spot) and /industry1 on a level least two z-levels underground (the z-level immediately below the surface is reserved for the farming level). The walkthroughs for those levels will guide you. Once you channel out parts of the surface with /surface3, you can start the farming sequence on the z-level below the surface with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." "" "This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, it will be easier to look at the online spreadsheets than this giant .csv. You can view them at: https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP" You are welcome to copy those spreadsheets and make your own modifications! @@ -42,12 +42,10 @@ Manual steps you have to take: Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox can manage the nestbox zones) Connect levers to the gates "" -"All blueprints are managed by the meta blueprints in the ""meta"" sheet. Each stage is meant to be applied after the previous stage is completely finished. For example, you can't build furniture until all the flooring has been constructed." -"" Be sure to choose an embark site that has an area flat enough to use these blueprints! "" Surface Walkthrough: -"1) Choose a tile for your central staircase. The terrain around that tile should be perfectly flat. Trees are ok, but no slopes, rivers, or lakes. To be sure that the tile you've chosen is in a good spot, set the cursor over that tile and run ""quickfort run library/dreamfort.csv -n /surface4"". This will show you the eventual boundaries of the fort. Some wall segments might be missing due to existing trees, but that's ok. Make sure the area within the exterior walls is flat. Run ""quickfort undo library/dreamfort.csv -n /surface4"" to clean up." +"1) Choose a tile for your central staircase. The terrain around that tile should be perfectly flat. Trees are ok, but no slopes, rivers, or lakes. To be sure that the tile you've chosen is in a good spot, set the cursor over that tile and run ""quickfort run library/dreamfort.csv -n /surface4"". This will show you the eventual boundaries of the fort. Some wall segments might be missing due to existing trees, but that's ok. Make sure the area within the exterior wall is flat. Run ""quickfort undo library/dreamfort.csv -n /surface4"" to clean up." "2) With the cursor on the chosen tile, run /surface1 to clear surrounding trees and set up your pastures. Remember to assign your dogs to the pasture around the staircase and your grazing animals to the large pasture. You can let your cats roam free." "3) Once the trees have been cleared, run /surface2 to set up starting workshops/stockpiles and clear trees from a larger area. Run ""quickfort orders"" for /surface4, /surface5, and /surface6 to get a head start manufacturing items for those blueprints. If you want a consistent color for your walls and floors, remember to set the rock material in the buildingplan UI and in the manager orders for the blocks." "4) Once the trees have been cleared from the larger area, run /surface3 to channel out the miasma vents for the farming level." @@ -57,6 +55,7 @@ Surface Walkthrough: "#meta label(surface1) start(staircase center) message(This would be a good time to start digging the industry level. Once the area is clear of trees, continue with /surface2.) clear trees and set up pastures" +stair_guide/surface_stair_guide clear_small/surface_clear_small zones/surface_zones "" @@ -84,6 +83,36 @@ query_buildings/surface_query_buildings build_scaffolding/surface_scaffolding #< build_roof/surface_roof +#dig label(surface_stair_guide) hidden() use the meta blueprints for normal application +j +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i +#> +i #dig label(surface_clear_small) start(23; 25) hidden() use the meta blueprints for normal application @@ -133,34 +162,6 @@ build_roof/surface_roof ,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,` -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i -#> -i #zone label(surface_zones) start(23; 25) hidden() message(remember to assign your dogs to the staircase pasture and your grazing animals to the large pasture) use the meta blueprints for normal application From 83e8755e6d9877be076ab380bf2b7cacb504a796 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 23 Oct 2020 20:26:37 -0700 Subject: [PATCH 038/112] fix typos in quantum alias --- data/quickfort/aliases-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index d5f52efcf..857160711 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -50,7 +50,7 @@ enablesequence: e{Down} # clothes and armor in this quantum stockpile will rot away. If you want bones # in your quantum stockpile, apply this alias to a refuse stockpile (but don't # put useful clothes or armor in there!) -quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{furnitureprefix}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}}{enableweapons}{enablearmor}{enablesheet} +quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{enablefurniture}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}{enableweapons}{enablearmor}{enablesheet} ################################## From b4498a262c06f96e62e5e049d3fab5edfbc656c6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 23 Oct 2020 21:12:04 -0700 Subject: [PATCH 039/112] update more dreamfort help, add automation.json --- data/blueprints/library/dreamfort.csv | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 914562093..096a6657e 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -17,7 +17,7 @@ It can be difficult applying a set of blueprints that you did not write yourself "" "Dreamfort blueprints take care of everything to get the fort up and running. You don't need to clear any trees or create any extra buildings or stockpiles (though of course you are free to do so). Blueprints that do require manual steps (like 'assign minecart to hauling route') will leave a message telling you so when you run them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" to start manufacturing needed items." "" -"Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should also avoid aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and about 10 blocks (of any material) to speed up construction of starting workshops." +"Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should also avoid aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and an anvil for a forge." "" "Directly after embark, apply /surface1 on the surface (see /surface_help for how to select a good spot) and /industry1 on a level least two z-levels underground (the z-level immediately below the surface is reserved for the farming level). The walkthroughs for those levels will guide you. Once you channel out parts of the surface with /surface3, you can start the farming sequence on the z-level below the surface with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels." "" @@ -53,7 +53,7 @@ Surface Walkthrough: "6) Once all the constructions are built, run /surface5 build gates, furniture, the trade depot, and traps." "7) When at least the beehives are in place, run /surface6 to configure the hives and construct a roof." "#meta label(surface1) start(staircase center) -message(This would be a good time to start digging the industry level. +message(Once the stairwell is mined out, you should start digging the industry level in a non-aquifer rock layer. Once the area is clear of trees, continue with /surface2.) clear trees and set up pastures" stair_guide/surface_stair_guide clear_small/surface_clear_small @@ -1200,12 +1200,14 @@ 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" "If desired, set the stockpiles in the bottom right to auto-melt. This results in melting all weapons and armor that are not masterwork steel. This is great for your military, but it takes a *lot* of fuel. If you enable this, be sure you're in a heavily forested area, enable auto-chop, and set up manager orders to keep your coal stocks high." +Download automation.json from https://drive.google.com/file/d/17WcN5mK-rnADOm2B_JFpPnByYgkYjxhb/view and put it in your dfhack-config/orders/ directory. "" Industry Walkthrough: 1) You can start digging out /industry1 immediately after embark. It is best to choose a stone layer at least two layers underground so the boulders can be used by your starting workshops to manufacture needed items. "2) Queue up manufacturing by running ""quickfort orders"" on /industry2." "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 to the jugs, pots, and bags stockpiles on the farming level." "4) If you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt." +"5) Run ""orders import automation"" to use the provided automation.json to take care of your fort's basic needs." "#dig label(industry1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /industry2. Once the area is dug out, continue with /industry2.)" @@ -1321,7 +1323,8 @@ query/industry_query "#query label(industry_query) start(18; 18) hidden() message(remember to: - assign minecarts to to your quantum stockpile hauling routes - 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) use the meta blueprints for normal application" +- if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt) use the meta blueprints for normal application +- now that your industry is set up, run ""orders import automation"" to automate your fort's basic needs (download automation.json from https://drive.google.com/file/d/17WcN5mK-rnADOm2B_JFpPnByYgkYjxhb/view?usp=sharing and put it in your dfhack-config/orders/ directory)" ,,,,,,,,,,,roughgems,nocontainers,`,`,`,`,t{Down 6}&,`,`,`,`,`,` From a2f943e7e5eba65f574ef2e0661f1f2c45979eda Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 24 Oct 2020 09:19:15 -0700 Subject: [PATCH 040/112] don't shift UI down for track constructions automat doesn't have UI there to avoid --- plugins/buildingplan.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 910e5ebe4..501594c4a 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,3 +1,4 @@ +#include "df/construction_type.h" #include "df/entity_position.h" #include "df/interface_key.h" #include "df/ui_build_selector.h" @@ -641,7 +642,9 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest int y = 23; - if (ui_build_selector->building_type == df::building_type::Construction) + if (ui_build_selector->building_type == df::building_type::Construction + && ui_build_selector->building_subtype < + df::construction_type::TrackN) { // try not to conflict with the automaterial plugin UI y = 34; From 3e0b0f8078656c6492581f0d1f2c24ba4101ba65 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 24 Oct 2020 09:21:18 -0700 Subject: [PATCH 041/112] update changelog --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 764d4cd46..37cbe6b94 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: - `buildingplan`: all buildings, furniture, and constructions are now supported (except for the few building types not supported by dfhack itself) - `buildingplan`: now respects building job_item filters when matching items - `buildingplan`: default filter setting for max quality changed from ``artifact`` to ``masterwork`` -- The Dreamfort sample blueprints now have complete walkthroughs for each fort level +- The Dreamfort sample blueprints now have complete walkthroughs for each fort level and importable orders that automate basic fort stock management ## API - `buildingplan`: added Lua interface API From 58917b03d167ba96afd5e5d66e7ee6ab7a4b0a20 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 24 Oct 2020 14:27:33 -0400 Subject: [PATCH 042/112] Switch away from CheckDFObject in unprotected calls See https://github.com/DFHack/dfhack/pull/1674#issuecomment-716028460 --- plugins/buildingplan.cpp | 5 ++++- plugins/fortplan.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 82551be23..3390b04b0 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -303,11 +303,14 @@ static bool construct_planned_building() return false; } - auto bld = Lua::CheckDFObject(L, -1); + auto bld = Lua::GetDFObject(L, -1); lua_pop(L, 1); if (!bld) + { + out.printerr("buildingplan: construct_building_from_ui_state() failed\n"); return false; + } planner.addPlannedBuilding(bld); diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 07685ca2b..8b8dc5584 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -72,11 +72,14 @@ struct BuildingInfo { if (!Lua::SafeCall(out, L, 4, 1)) return false; - auto bld = Lua::CheckDFObject(L, -1); + auto bld = Lua::GetDFObject(L, -1); lua_pop(L, 1); if (!bld) + { + out.printerr("fortplan: construct_building_from_params() failed\n"); return false; + } planner.addPlannedBuilding(bld); From a0c86b8a9bea6fcf9e0e04fb3dd4665329a5407f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 24 Oct 2020 21:14:09 -0700 Subject: [PATCH 043/112] rewrite quickfort user guide into spend more space highlighting how users can use the blueprint plugin to avoid learning how to write blueprints themselves --- docs/guides/quickfort-user-guide.rst | 54 +++++++++++++++------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 2dfaa74b1..9e5db34c3 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -5,32 +5,38 @@ Quickfort User Guide `Quickfort ` is a DFHack script that helps you build fortresses from "blueprint" .csv and .xlsx files. Many applications exist to edit these files, -such as MS Excel and `Google Sheets `__. You can also build -your plan "for real" in Dwarf Fortress, and then export your map using the `blueprint` -plugin. Most layout and building-oriented DF commands are supported through the -use of multiple files or spreadsheets, each describing a different phase of DF -construction: designation, building, placing stockpiles/zones, and setting -configuration. - -The original idea and 1.0 codebase came from :wiki:`Valdemar's ` -auto-designation macro. Joel Thornton (joelpt) reimplemented the core logic in -Python and extended its functionality with `Quickfort -2.0 `__. This DFHack-native implementation, -called "DFHack Quickfort" or just "quickfort", builds upon Quickfort 2.0's -formats and features. DFHack Quickfort is written in Lua and interacts with -Dwarf Fortress memory structures directly, allowing for instantaneous blueprint -application, error checking and recovery, and many other advanced features. +such as MS Excel and `Google Sheets `__. Most layout and +building-oriented DF commands are supported through the use of multiple files or +spreadsheets, each describing a different phase of DF construction: designation, +building, placing stockpiles/zones, and setting configuration. + +The original idea came from :wiki:`Valdemar's ` auto-designation +macro. Joel Thornton reimplemented the core logic in Python and extended its +functionality with `Quickfort 2.0 `__. This +DFHack-native implementation, called "DFHack Quickfort" or just "quickfort", +builds upon Quickfort 2.0's formats and features. Any blueprint that worked in +Python Quickfort 2.0 should work with DFHack Quickfort. DFHack Quickfort is +written in Lua and interacts with Dwarf Fortress memory structures directly, +allowing for instantaneous blueprint application, error checking and recovery, +and many other advanced features. This document focuses on DFHack Quickfort's capabilities and teaches players how -to understand and build blueprint files. Some of the text was originally written -by Joel Thornton, reused here with his permission. - -For those just looking to apply blueprints, check out the `quickfort command's -documentation ` for syntax. There are also many ready-to-use blueprints -available in the ``blueprints/library`` subfolder in your DFHack installation. -Browse them on your computer or :source:`online `, -or run ``quickfort list -l`` at the ``[DFHack]#`` prompt to list them, and then -``quickfort run`` to apply them to your fort! +to understand and create blueprint files. Some of the text was originally +written by Joel Thornton, reused here with his permission. + +For those just looking to apply existing blueprints, check out the `quickfort +command's documentation ` for syntax. There are many ready-to-use +blueprints available in the ``blueprints/library`` subfolder in your DFHack +installation. Browse them on your computer or +:source:`online `, or run ``quickfort list -l`` at the +``[DFHack]#`` prompt to list them, and then ``quickfort run`` to apply them to +your fort! + +Before you become an expert at writing blueprints, though, you should know that +the easiest way to make a quickfort blueprint is to build your plan "for real" +in Dwarf Fortress and then export your map using the DFHack `blueprint` plugin. +You can apply those blueprints as-is in your next fort, or you can fine-tune +them with additional features from this guide. See the `Links`_ section for more information and online resources. From 51f49b1c2cf7b728b2acf1181ea4d10e3ace22c4 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 24 Oct 2020 21:17:53 -0700 Subject: [PATCH 044/112] add bedroom and tomb blueprints to the library --- .../28-3-Modified_Windmill_Villas.csv | 69 ++++++ .../28-3-Modified_Windmill_Villas.png | Bin 0 -> 5757 bytes .../48-4-Raynard_Whirlpool_Housing.csv | 99 ++++++++ .../48-4-Raynard_Whirlpool_Housing.png | Bin 0 -> 546 bytes .../bedrooms/95-9-Hactar1_3_Branch_Tree.csv | 231 ++++++++++++++++++ .../bedrooms/95-9-Hactar1_3_Branch_Tree.png | Bin 0 -> 4312 bytes data/blueprints/library/embark.csv | 11 +- .../blueprints/library/tombs/Mini_Saracen.csv | 26 ++ .../library/tombs/The_Saracen_Crypts.csv | 98 ++++++++ .../library/tombs/The_Saracen_Crypts.png | Bin 0 -> 11666 bytes 10 files changed, 528 insertions(+), 6 deletions(-) create mode 100644 data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv create mode 100644 data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.png create mode 100644 data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv create mode 100644 data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.png create mode 100644 data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv create mode 100644 data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.png create mode 100644 data/blueprints/library/tombs/Mini_Saracen.csv create mode 100644 data/blueprints/library/tombs/The_Saracen_Crypts.csv create mode 100644 data/blueprints/library/tombs/The_Saracen_Crypts.png diff --git a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv new file mode 100644 index 000000000..006c01f55 --- /dev/null +++ b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv @@ -0,0 +1,69 @@ +"#dig start(11; 11) 28 bedrooms, 3 tiles each (efficient layout)" + , , , , , ,d, , , , , ,d, , , , , , , , ,# + , , , , , ,d, , , , , ,d, , , , , , , , ,# + , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , , , ,# + , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , , , ,# + , , , ,d,d,d,d,d, ,d,d,d, ,d, ,d,d,d, , ,# + , , , , , ,d, , , , , ,d,d, , ,d, , , , ,# + , ,d,d,d, ,d, ,d,d,d, ,d,d,d,d,d,d,d,d,d,# + , , , , ,d,d, , , ,d, ,d, , , ,d, , , , ,# +d,d,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d,d,d, , ,# + , , , ,d, , , ,d,i,i,i,d, ,d, , , , , , ,# + , ,d,d,d, ,d,d,d,i,i,i,d,d,d, ,d,d,d, , ,# + , , , , , ,d, ,d,i,i,i,d, , , ,d, , , , ,# + , ,d,d,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d,d,d,# + , , , ,d, , , ,d, ,d, , , ,d,d, , , , , ,# +d,d,d,d,d,d,d,d,d, ,d,d,d, ,d, ,d,d,d, , ,# + , , , ,d, , ,d,d, , , , , ,d, , , , , , ,# + , ,d,d,d, ,d, ,d,d,d, ,d,d,d,d,d, , , , ,# + , , , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , ,# + , , , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , ,# + , , , , , , , ,d, , , , , ,d, , , , , , ,# + , , , , , , , ,d, , , , , ,d, , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(furniture) start(11; 11) 28x doors, beds, coffers, and cabinets" + , , , , , ,f, , , , , ,f, , , , , , , , ,# + , , , , , ,h, , , , , ,h, , , , , , , , ,# + , , , ,f, ,b, ,f, ,f, ,b, ,f, , , , , , ,# + , , , ,h, ,d, ,h, ,h, ,d, ,h, , , , , , ,# + , , , ,b,d,`,d,b, ,b,d,`, ,b, ,b,h,f, , ,# + , , , , , ,`, , , , , ,`,d, , ,d, , , , ,# + , ,f,h,b, ,`, ,f,h,b, ,`,`,`,`,`,d,b,h,f,# + , , , , ,d,`, , , ,d, ,`, , , ,d, , , , ,# +f,h,b,d,`,`,`,`,`,`,`,`,`, ,f, ,b,h,f, , ,# + , , , ,d, , , ,`,`,`,`,`, ,h, , , , , , ,# + , ,f,h,b, ,b,d,`,`,`,`,`,d,b, ,b,h,f, , ,# + , , , , , ,h, ,`,`,`,`,`, , , ,d, , , , ,# + , ,f,h,b, ,f, ,`,`,`,`,`,`,`,`,`,d,b,h,f,# + , , , ,d, , , ,`, ,d, , , ,`,d, , , , , ,# +f,h,b,d,`,`,`,`,`, ,b,h,f, ,`, ,b,h,f, , ,# + , , , ,d, , ,d,`, , , , , ,`, , , , , , ,# + , ,f,h,b, ,b, ,`,d,b, ,b,d,`,d,b, , , , ,# + , , , , , ,h, ,d, ,h, ,h, ,d, ,h, , , , ,# + , , , , , ,f, ,b, ,f, ,f, ,b, ,f, , , , ,# + , , , , , , , ,h, , , , , ,h, , , , , , ,# + , , , , , , , ,f, , , , , ,f, , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(rooms) start(11; 11) room designations + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , ,r+,, , , , ,r+,, , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,, , ,r+,,r+,, , ,r+,,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,, , , , ,r+,, , , , , , ,r+,, ,# + , , , , , , , , , , , , , , , , , , , , ,# + , ,r+,, , , , , , , , , , , , ,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,,r+,, , , , , , ,r+,,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,, , , , , , , , , , , , ,r+,, ,# + , , , , , , , , , , , , , , , , , , , , ,# + , ,r+,, , , , , , ,r+,, , , , ,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , ,r+,,r+,, , ,r+,,r+,, , ,r+,, , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , ,r+,, , , , ,r+,, , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.png b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.png new file mode 100644 index 0000000000000000000000000000000000000000..dda3673e26f2926131ebd84013f2eac6e293a3e1 GIT binary patch literal 5757 zcmZWr1z1#Fw?0Tpi!|Z~(l~T?w}5oVfG{*D4N}q}-3VB8*C5>?$c%*45RxN|gc3t@ z=kvSY{qKG5bIv~d+0R~k@3r3dJ!`M{r#fmRg!F^}0FY>?E9+zWY)l&^z{7kAqiG#5 z-CZwrGj9MOy8m~>0`lL`Vy1MRm6V=7b@KG_^mg*}V$x7jV)A<7>FDf+SrM>UXaF%Z z*rAp|uN*6C#f4{TdFoRUFzG8MhEt}ob2Hy1*NkI+w?L)WcTYtHhhg+>9CmVYcoLN! zh&X|89)FYheRAZ7xP-CGwcrn~vz_SeiJO)=S!DH5VG|s`mw-51T}V$PoVY^q;l1yX zBZI?ho4oQ7gsffwIYHAiX76hjEZ{6uTKe&$Ui@wVD`1+40O-{&=;eP9drP*fm}iL< z9);EGlP0c35Kalm2PG?(0`jU@;f1;E2Ebb!z;@i;ZUcDA1K4teZBGN?g=cxeSb#+) zGZog`G=S;8L!2_;Bn4E%jT2M=Q+|NbQEN~FnCAibHB21Uf%;aU7e-Fh2oMkh{CaWG zTmVimU^~jf5&%Ty0hFr0O{9N4t|douW01;glx|~{P>HZ0;CYU3V#5276Q)7)m{Jsc z2b?d@+aH)mCm112_WkG!0K843!bE#?9RMS(g~3FVp`I=Znof6Uh)92 z<`X)31LA9<50}CVcfWbhwvXdvP4w0mo#@y|Cf@?Q-B~m_^70_}UZv4E%1Bwmept~9N;|-6h ziK~X`>y4|iJK|65;SK&c6cH-DcSUjh6`8;f71T|inCi@{moq(J6~4E4SL`l!lwLo_ zql|K`X7=`b=YzC%f&#Io>L8X`ij@0&J}*9VZN!(z1Tj4xjovKW9M}}vWY}c>Z9!OQC;$B8nZY`z{+Np<-4^+l z@D`p!uAqX^hkX6z@^^;!L{iKZYu-Wi>-1vF?+g3pOH4#kYiO0!yw94ZccO7ZJJ(j@pl$AK1tK#3V6Pkt1;sPPzhGd|C*dKW$y^lTy0rM7j|}elY=5 zE;5fS&(p}18_NBXDs7A~OEZf-i-DWZu%fcGa;1{U=*Vc?Fr~uUAf#5+NXTHL;_gg# zl~HBkQ2?rA9ZG1Tlj9?xvx$$+lXN4>WVHXEJ8Ohoo{O#vWgSCXn2 z-w2UG_J)atmwwzGxz~&xi87TH?^p7$f4IvCg5P{TA7`bZCa&XrB|hf>JqKpD=RGRQt2?ej38AQ(d4k?P=lnva0`(J z3kJ(&gk{v)GLk2?yp^sc4DULW{d+vh_H&E6t;36+c$TdMb|Ehwgz#M(Ub|nEov0C; z;x!So;vtA(w9-Q&&po9X`9=`G^pvayT+tsI<08=?~vjU#pTOIS*TIux_{y zyAOY0$75$^*V5kA-p|+9ZY$*~m3kueWMtyY#QTZaJdpw!fjL2B0kUAOV71lOJir_T zEr)(`&}iH=FE!hTc6vmcOPH0I8kt))sn!(Mu+4VVbd=pIYtzZlsVbU<4AlREeuh@G z($#Ox;~{FH?$8i0#Eu5iI#fHbpV5^O(jM~qS_Ypac7e2t(VQ{VyZHsBq_N~mKJ3lD zT*%OI_6{G!6iyDG7utZ{ALkl$E;J~ZR)f$$+Pou=;u7PvUXAnS$nq923@?v%ZEE_Q zJhxg4nmQ>gXe#L6D@WAri|#w`$1W9r5iaH`G86LtIxwpAU2khrj;J_`-*vS*R98ti z0@8wH;j`yUGpU)YZ!Ph!M1v1#WKU&Z6mQ6GNQ4UR4TX zxqWsUbUO)@g|lHnu;U{XA}sD)o&~*R?n#!DL^zx_F6=2Jy=_yVQb>7K^J=TFhS_QK zXKD>I8kq}AgE^B%KMnmc`xPI&4Ne>gCFY2ZjW&sqOZJL0rJoeY5%Hf~Qu1C^zEI9q zUMYYG=t|V_Xo_&lR`R+?FN(Dbx62LlI+}g?W?E>f;imOKf|t8i)WfZN^=j`ZcD-<& zXd{>=mv7X{EASnx|2Vpgx`LMiB$+M$Bu+;>^H71DyPP_S?e~3uwq(M_WC3{l=>uq8o39JQ7>^9%kWxc5O(1gesQ)Ff`|!$q zS1tW10m|+EO}A>#B&RmCi#{VY2mMeBOUpx`=;=~pNB-0#OU>#;dK@-<-pAy^xakTLlVhFzog*mHT>zn z{1_pOd@hRlzw=cK4Dv56>@3=KzsOF`?ydw~sUiL%dm}7u7N~h-_}O`J=hbuuml=pne7V#b$jI@WXx{!Guw3GX+O{v@_BBeT|V3xl>`TTA~Y~45cWRhPeY2&`@ zvnzd*F|9k&@unlcw$0MDZ9k~1CD;+Y!%!p!TR!dz{6&2``>jYqX1VJq$P+z;K3Sz+ z4t>FK4IA%R@aNdC>D2ACIhNm3SWsGy{U&E|vowFO6z0XW+`PXTuce{p3qAXWnuAT|PQJ_eos-ey z{<7qHw--fMhZyc3e%SpvKuEy)*!Xg<&wt6E@!-QMneOIGrZCQ1r}KsrgRfa3jM_2YjJP1s^i33s4Rsev8Iv8JIDXy}G_O00glCKzJkooZn*l9RTp<2Y_8$ z0FcZD02=-~$U_ z$rG^SnEC+ZccOrT);Z92W^(UW_mfD`{hJubiXh@m^=)Brhk zcs<6T*Q2W+GBkQhUEs|FuNtHVH~hN_voIjq#vWx_fI3GTKQsk}za#(muT;F=9>dx$l6#A@`=9a=)HDBM4R z9gcVQvl%D0v<*WaboI21wJmzbC2v_;-uK;@|I4@l>pzx2-J$DBREN~@_9jp5$t=fhr8Bm=AI`0$qPI(V#o{GzM45EUq9rEF&Eh^ z6E0@gKWJl2yGI^dPS6DzyP-5(U$keBw1k|V$|_eY5dI1HAAqgbIAC8B2cb~d)zXE8 z+CP&RKK=mmGJXh6C5yU_wCG8JpI^tox#7bCeJ%3ULWF}t&; z#=g!plJY;ZKxcne7lT?Iq=j}tF$w-{9D&F${>S29a!j*^i1kg3Eh1+o8D5007TXVR zr_;2ZpPO*Qn*<~Nw@4yr){wOd?S@4VnL6}Gv{1ht7I8H2M+O*`t!_|xFlLyeu4)^g zH47Gjw*M`!Ke`Nvt{a+N$vC>i@&AAN3W#>?vI;@1hGZS}{+}J|wy0`xXDKnH{zt^& zLoTkVcYwryOy*9k&Uf3{OJ*t^&WJD3KmEAI(;Gn@moE{} zOxDjo+b+7xI1^M?znL4*#@v=$SDAxW(Hfe8a|EubrCf|DBa;#`@DCfm)QgaMbG}tq zFn;10VUqLiFcW;PEvKH$^vcTjm6gUk1CUQQ)2_CW29P$k@>v7*fcpZZ3p0 z+Q>+c9B12;8ApSw!a09(s2r8wJo~Yw6tO2t;b@kFwlXWPmb5(E>(#L?CZMamlr;~#GvK?m7*X7A6nF<78kW^S5NXx#Z zWp?=eC-Zb^_-FLBUT|amZ|O2HEp^TE5`kS#id23`wL3zWB%!JP=Y2c{dF|=1{KSrb z5$$-1f8FtURFWw$t&yHe{wjHdrBJ7AAx3LDb6vc&7PTK#L;lL6m0>GTrwBQD{<4%+ zgOlKn_vExN8~jMsu6Rm#VpOsr{O848W=z2v6WCp zgVkdPB6xEW>597Tq7&cK-c5`)rW_g=@Jwg*X|o(X>eM^`nEAmT6CCld9JR3?r9aIe zuei;3)QL;X1JUX30}Mf#NC{snNOD^*Nlt0SJxMVq+8ANtND1nZU$KmX4=(~nUGs;p z@D7@!CYrIm3J#HfabeFDkM~kX-`?!LLj-nh=ItAM7&cyLs7Nad@0d^idI8Di~;|;8sNu(##+K!lDUIp zWJLF_p+0-DPd3M$P4@ACVfht0J={t)15Ep5B-igkEmdDX+rjZuD+!@R1=O8uym8Xf zCksL1))H`b<}|wSs?-@1^`xI{agPsk>78}36_@Xvvvq_te4?DW#E1S~fXA2NwHIwk z$;CPxlErXCBPtAt?ubw)tJ{k)+_eV(#ls~jceox#0E0^cb8Uk&-USbvYDUzMNDT=x zIs-B-g0&@Q%klc?`b>RFm9*!o52FzgT_bG=#zrc0s4UXhf$FBilqd)EZbb!8!mIXL zq0)3DqjX9=AN^n6?&1iARDF8gbQzY@ui5*k`PYL=<;x2BrOeEOIn`ONbh9xWzreJT zWRU~>spS1n#N5czWxV}BXR9faD>?g}-k!Nq0%Tf0(`i(|>i?6K|KuYASBog}4>Re`3B!ASm&e20t4Q<5Cd5|&_!py1 zGWK?$j)PhL*HIQRMu{y)c!O+rfz0w)qwt6|W7ift>fC+j$lR3?r_ae|6)l>A@aG#b6&zTX6< zb-4~Z5oZj}a`&gM;v~#y?^9Y>^Li_+z4KDGpk5=tG}9sPGmB)QPU0`MRrF3!S2{9~ zeIlmke#}~I-;9h`$FgISlH$jeK7A?p5MWV>k^cD!7n-El%VEnG6a zBdnQL7(5ki5YYq*7FUx_w$E_ri}(#Wb`m1X`_fSAwGuSv%Pu8CaEI`fZD!3_9|UnB z+cfY~)AWiwqR=x7DM6+3oP!?oFT^{KZ2Vfg`XHV>@l;(ImIA5yEbRQ;gSHjbavXMZ z+4Kcw!&O~bS&nK?qbbi6By*pCJz-IVWH;JMQ=Mo`+HHSWaxZMo-pU5don3MZk3Uqy z)FTDtcAM7?_@g5ktOA#fAfaNeas3>J3eQ$Vp@~zlsbZX&?(YuNdAUihL!Jl17(>Rv zWqYkFhNlXaR+2DCElhV&-TuQeVPf3zo*#oR(>{SF*D|zb)*RJ+;7c3R(PkB)EO!{( zsff8@{`-h?}(4Q=gPb3i8g8S2u&Kf)SIJ`_F-43 zZ}|BWO|D8*kT6epJt&~i0H&C$dS>>Nck)K&BkXWY^fdh`sGe2hi=B%m*Ty*}^^RTL z>oh%F^WsNFViu5QVX`od7)9A5OvYEyOWLN8#?Lz{Kh2aJFm`QUe{JPwZ4c5m$Vk67 zD>w86 GtN#L58|9b) literal 0 HcmV?d00001 diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv new file mode 100644 index 000000000..9329353f4 --- /dev/null +++ b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv @@ -0,0 +1,99 @@ +"#dig start(16; 16; central 3x3 stairwell) 48 rooms, 4 tiles each (more aesthetic)" + , , , , , , , , , , , , ,d, , , ,d, , , , , , , , , , , , , ,# + , , , ,d, , , ,d, , , ,d,d,d,d,d,d,d, , , ,d, , , ,d, , , , ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# + ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + , , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , , ,# + , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,# + ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, ,# + , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,# + , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , ,# + , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,# + ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, ,# + , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,# + , , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , , ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# + ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, ,# + ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, ,# +d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# + ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, ,# + , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# + , , , ,d, , , ,d, , , ,d,d,d,d,d,d,d, , , ,d, , , ,d, , , , ,# + , , , , , , , , , , , , ,d, , , ,d, , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(furniture) start(16; 16; central 3x3 stairwell) 48x doors, beds, cabinets, and coffers; 8x statues" + , , , , , , , , , , , , ,f, , , ,f, , , , , , , , , , , , , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# + , , , , , ,s, , , , , , , , ,s, , , , , , , , ,s, , , , , , ,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,# + ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, ,# + , ,d, , , , , , , ,d, , , , , , , , , ,d, , , , , , , ,d, , ,# + , , , , , ,s, , , , , , , , , , , , , , , , , ,s, , , , , , ,# + , ,d, , , , , , , ,d, , , , , , , , , ,d, , , , , , , ,d, , ,# + ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, ,# + , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# + , , , , , ,s, , , , , , , , ,s, , , , , , , , ,s, , , , , , ,# + ,d, , , , , , , , , ,d, , , , , , , ,d, , , , , , , , , ,d, ,# +f, ,h, , , , , , , ,h, ,f, , , , , ,f, ,h, , , , , , , ,h, ,f,# + ,b, , ,f, , , ,f, , ,b, , , , , , , ,b, , ,f, , , ,f, , ,b, ,# + , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,# + , , , ,h, , , ,h, , , ,b, ,d, ,d, ,b, , , ,h, , , ,h, , , , ,# + , , , , , , , , , , , , ,f, , , ,f, , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(rooms) start(16; 16; central 3x3 stairwell) room designations + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# + ,r+,, , , , , , , , ,r+,, , , , , , ,r+,, , , , , , , , ,r+,,# + , , ,r+,, , , , ,r+,, , , , , , , , , , ,r+,, , , , ,r+,, , ,# + , , , , , , , , , , , ,r+,, , , , ,r+,, , , , , , , , , , , ,# + , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.png b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.png new file mode 100644 index 0000000000000000000000000000000000000000..24eb4a22af2addc52ef792038d1145c2a0af2841 GIT binary patch literal 546 zcmV+-0^R+IP)500001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA0k=s+K~z{r&6Yt< z12GIm2X27acFBP_DhFcOE#e5^ANni%$H5*!VV-1tJ9eDZ?F`&)+xB602<|U>2!3># z_w&oe`$FTrEH6_X#Q5#>G<+!N?#oCo(>dtj^gQ(O{2G+&pwmae4|tdrO}?u7Rjm87 z9PK(8O9b_w5HQxdO1phPc(gG_j@uUzHd@tHd6LzEaG7l5t?`pP=47&_0P7kPtXDCq z`jGKUhFQVntEykcxar$B&w91pJF3XeYGT8yQLMMCN$>^pkCRo>) z?CAr>FPUbA?U=r*`kfdyy=Qn+D;`Q1YaZkFx~e0Z_oOeq>L9>o{NAblkWL@vIwmWc zd{y3R(MLDtEbW~h*;kRFqLZHmVrOXbNL!eBH;M(LFq z(-YAuYYZ(jQMLx5rxA^%_x5()^Esb$KIfc2?$7VOuHSuMzia(o*L`PUk2p(7?2-Tg zK+4s{5eEPwD&Vyj1_3!0^JTlhMI;*Mj0PBeDtwTDhS+250pQ+s$@M@{kXAeGg2Mnn zk`4f*kO4plvQj<+K&%k}%$@=Os}cZEj3{sOvH<{ux2vN)KH=n-GW#3c7t#(Sfu%5D zc>PM8fF^3{qHpr!q1h9yJtFmPXMg_a+5mb1+6_Blcbh|{oN+$-sR*tKX~d-Efmd244Ms| z7F1P`22eV;A@p=mwCq%zG=wZ6gQ_Y4C`d6kOVCX=Kxz<#OjpvIQd=ra`Fm`_KZrdX zGlP~9$?7l^`TY4Pgbafirb^#mzzv;ESB1!?`a=c=Z$n{&iBRuL^;PTV1r3WAUiuMx z9WPhZlzjFKvYYy;rvKO_DMG4IFfrmGIJSg>WKrOpOfZ#=lwP`!XB|8f|6rN#aH^cY zc3x3?U z6eJ{@1?Th^_}LNIHGS1kD>UaT{y0mAa$=Z!p~Z=ou(ndPa?Q3FyMunZcVpxfIUD=e z)L-F!u@2Q2eOxozg%Lf=94yvI7TyE%RTAUv4uR{<(u=b zrZP5*p*OT|o$*#2^S!UT78T<7(;d=%V_@ECyx4oZ83{>#n@&`K5I$7(?T72Rqwg!G z2AoKJMTgCwRcs@`@QGwu473sEp;32Nt5TA_{zTmNVjLF zO}4gpfxD=6)vgtQ+XfQzdgkNzQp<{>#qKF=(s?a4)-(Ua+!Ske`#mUrd%;GB@r3nY(Q6Q(-*G$YP>uK?!juB4R{0z^Uq zh)eS3nHV(`P3QNU%6qg?G}WiQfow!Xdq19U{b6U^@SV2E`A_1)y=qtm*%c{YXTT~< zmJZ3s7}(Puwa58fswtb6n>GWp8-4bTXgzwo5vxDbw##l)f4B3{5tK2p^*CrCCcg5^ ztML>Q*qu|}RBJP zrn9;}s0>%2Bn?LlNzbirRt?9bk(PP&VF6$)L%cv3{4E#2Gst?a$i&(VhK|Nze}mjz%qnscYd$o80E8poJ$M zFInU;_lDkcR>tNO;efkEPvpBQ*Fr4eoa{G!+n7nu zv;SFn>XBAHd(OZFS=zPlX+k!fZpy1$WQ;u)5m$9V8Fv>Rmy^DJXZRNt=Fh z)zqFovM>OeHmfDBYT+J4L@!f{BP<4RxsG5Q6Cx_f#y{>^2 z4(kENly#a(iW_Yg?s&-dj5ieQM?_$jif(f4{0oR1trpB^%H`A!!{Dg37_YWvc%gej z-X%p77OBl3W=;-Syo45>dE33X&!_#KpE~ovQJgbhPP#pr(wtW~dQQ}2qtC;zb+Qf{ zyKR=qx-&7EZWY=4an{pJPG#qTuVIbM>4;^}>Sxw=pOBs=6?hqHzf@NBm{YXED)Jj< zd?^pJVs-fV!}E63X^CC&hCc0`xo$EGe#w^W=|t-)(BA`E!6zs`>8i5Q`;WzKXcj(Y z4iuIegmPJa8xkf+TV{0Q;%u??MOmS1z<@vT_M^LrZy%jkT`cY5REFtT+~Ln?w?%;p z!4;Z@i;aZ&7^ZsUGKg}v{F$jf+@o(y3|sx-?Bm8EUP%0wFU;+WyDV%eLd&)+b#57i z;ug9;W*-@n% zJgeUVBa$r$W46W3>;+YUyu1OtaP#ZDt2fW{0ntH@zPlB_ucNVM?=!Q?_g(eE1y%ts zb_Kh>KKLq@ODZg7)P;!=c7l%4e_Df8s9~ zgIkJ@PWvg_`O;gPL>V6;-RfUy+xwW;RPC8GBIi{#uXMPcA0}I6Q*S4znEaPORlO_O zC##c7&iwhZr90p4w!lA;N zSw|l6k87439q2v|_P)htq5EQnBP&NsDz>ESi*w)s%*25^uj5zk>+9!NqMl6#Q7sjT z&b_tb>HW~FukN~yeH5qGH6KNv{r$Lz73TD~K_OBkLIxj(T+_yjC5nDFy_(vDr$akz zv7i8RiNYvg7t8~k4(r0p6W@c4J?@E$(N%y_`XF*TTo;iZmcB=Yx4$Oq5oz~s)x0GS z2H_-fZcRHzy<6?=rMmjzZ(pl$&x+-5{uufNotVOPe6)m;cr zls=3P_WQWj`2KdaPh5OUO@lD&1Mfnu51`$p7cn-gF5*lpXe60-OC6~%S~YsxLJD6o zBF~0&f+2#okwV2aSyjnOAb2L=HZsMz&l$3KpNa2xJ2eWG8>XY$Gh-?ndKSWNROE|a zCp8`@vChQ}ahSFUdVG67bB!R#5}!Y%r5N)oUj`;MLpiL4{90cy9oYd+m4fm!PaZuu ztppAUt|_wg#r@mp32n(90@waHqZ^V1>iL1P4!Wa%f4y+)`elQ8jh9zQ`zXSjD2^0$ zZKB+_J=t(7;|(wEw+y-@2wk&F0Huv`BGx%_PA}*97wIp3t3{92{LX^qfVWL3YKRs! zKWECiuags#9Je;lXj1ZLxw1&evHBaO6AW#<@VWb_3NdsGbi8bTm01^9W(pJ}jPUop zCmSA32n|{)wJ8hcCl7k#}yd8R($Ms=EACKkw^|?Dfb|kv^fkpdB%X~YT z-tpP9D8k_rSn`yl*ZuD0QyGqfi`6WNi=5}RaFrsXuVEOyocP|`!+!P?eN(eOC0_i; zx6Yf%>>u?Mj{#w*^ofUoAb{i08EEOyG?3S&1_X+KV~s3L3O}t5;LZS4@zY<=;F!rk$d>HiHxP$ zedinTwUlp_wg>}Z0L_`s9$}Wrye<{tFrLDYwUuHnsIzk-K5{B zfF~q(WfW1Stii#x!PMt3y$i1kRNLkZZ1qanx0Y7Qo9H_kN6M=vDSK?&c_dos>+9BCh0!w_fTDyf!3;DJ`)!J{rY1 zFC>yW1M@i3xxX01co(h^o@sexx9PcGS~B?TWq78&_vztD_jdQD_k!-lQ?C6BEuYnw z6jO<5Cpk??&4kALX%2)7!*6p@nKu(~;lYr1jo>h5XJc(KOw0~*m!k0r+p!*{+>o_>4dkt+!kMO!=U)5xp z!DC9AYQUj==uph^9;g*$z$+gcI2I0zfmVU+ch7L9LT`YZrxTGS{zrnpQ(#onfSDL`0MT2;hhlPkcVFW}po zC2Nq-D0eUWkGa7J5sg3Pl+Gv~Ccq|(s{iOz?p1?0YUmohFwCVRUhH!cO*bjEhhA|! zaw^YXiJ+e6n=K&`Vwwg+WR-WQv5n|xwN`lr`MyxG9cr?csrGS!`2YS_AXH=NP>kJO W$H%k8VDQ%i;Ocb5k%2z-$3FoF3IYlM literal 0 HcmV?d00001 diff --git a/data/blueprints/library/embark.csv b/data/blueprints/library/embark.csv index 17289dab6..a92c32839 100644 --- a/data/blueprints/library/embark.csv +++ b/data/blueprints/library/embark.csv @@ -1,10 +1,9 @@ -#build start(8;2;center of wagon) basic post embark workshops -~,~ ,~,~,~ ,~,`,`,`,~,~ ,~,~,~ ,~,# -~,wc,~,~,wt,~,`,`,`,~,wm,~,~,wr,~,# -~,~ ,~,~,~ ,~,`,`,`,~,~ ,~,~,~ ,~,# +#build label(workshops) start(8;2;center of wagon) basic post embark workshops +`,` ,`,`,` ,`,~,~,~,`,` ,`,`,` ,`,# +`,wc,`,`,wt,`,~,~,~,`,wm,`,`,wr,`,# +`,` ,`,`,` ,`,~,~,~,`,` ,`,`,` ,`,# #,# ,#,#,# ,#,#,#,#,#,# ,#,#,# ,#,# - -#place basic post embark stockpiles +#place label(stockpiles) basic post embark stockpiles w(5x10), , , , ,s(5x1) , , , , ,p(5x1), , , , ,# , , , , ,g(5x4) , , , , ,d(5x1), , , , ,# , , , , , , , , , ,f(5x3), , , , ,# diff --git a/data/blueprints/library/tombs/Mini_Saracen.csv b/data/blueprints/library/tombs/Mini_Saracen.csv new file mode 100644 index 000000000..e32381b82 --- /dev/null +++ b/data/blueprints/library/tombs/Mini_Saracen.csv @@ -0,0 +1,26 @@ +#dig start(6;6) room for 24 corpses +d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d,d,d, ,d, ,d,# + , , , ,d,d,d, , , , ,# +d, ,d,d,i,i,i,d,d, ,d,# +d,d,d,d,i,i,i,d,d,d,d,# +d, ,d,d,i,i,i,d,d, ,d,# + , , , ,d,d,d, , , , ,# +d, ,d, ,d,d,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d,# +#,#,#,#,#,#,#,#,#,#,#,# +#build label(urns) start(6;6) 24 urns (use burial script to mark them as usable) +n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,`,`,`, ,n, ,n,# + , , , ,`,`,`, , , , ,# +n, ,`,`,~,~,~,`,`, ,n,# +`,`,`,`,~,~,~,`,`,`,`,# +n, ,`,`,~,~,~,`,`, ,n,# + , , , ,`,`,`, , , , ,# +n, ,n, ,`,`,`, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n,# +#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.csv b/data/blueprints/library/tombs/The_Saracen_Crypts.csv new file mode 100644 index 000000000..a4bd7fc95 --- /dev/null +++ b/data/blueprints/library/tombs/The_Saracen_Crypts.csv @@ -0,0 +1,98 @@ +#dig start(24;24) room for 489 corpses +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,i,i,i,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(urns) start(24;24) 489 urns (use burial script to mark them as usable) +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,~,~,~,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.png b/data/blueprints/library/tombs/The_Saracen_Crypts.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8d6e03e5767a73f225323574b1a87ffe06eae1 GIT binary patch literal 11666 zcmeHN3s@6ZzE6$8-3=OUX#>_rbZxa;(WFp|C=x27)vLgEw+dpJpaSlt8kJViLPm0} zNF$F*Eum6^bxUt6H3$l|V1^hTBGHIeDTt1MJR~57@W^Xs?wLF=DBHUG)vDb+-v?iE za^{>f|M&m*|0f3pEF1ra7ydw@P{uERbIDs2${02H!+tjg%uIO>>84PoQI;=xAg53g+*~QSX*4RO|2U7b+lO4#@dZr_6If+>9eXhZu_iRCzLMdQ3G}59 zlCP4TlmsSV=MBvLGC_zWA(|^O7!D20aZSJqeT-KHJ_7@u>QIis5RowWEf(LU=3g!0 z^$*kxl7Xq5XLoa~));spvj)NAe(GfGZKh*EPF*744AnR$vs_T)ic?ckqHs;Z5W~G4~2_zNG1Cf$EG>;*Y@#cZaNamRb zT2FG^JdC86{6d=|o8%Pr!~{h{i7s&ulj*ppQo1tlS#J8pGs46I-NBl_wv2mrZ7|1) ziMVI2F!?T~(;d#VteN?jWY(LT!sWifrgIx#(NKRs$IbP3A}DL+OM+>KKcrE^HM29m zu3)@)qJTHeO%UOJ@R4l(-6nZH=Fnv!nwnAh>rw4rVg+(e_{>!g!|%@&3gWbvt&)Aj zEZMr0A|7v%AN#lPtraLhzjJso-Y5y`Q7wZ|bM2kyq^Ta;r0+R0TDd=l{OuCFFdi6NR>!z(Hf zUf#3At}F7HS=zP%%or#^%49HMz_AiqjP5v1;djqpF}Hd{oZ~M|=k1M8S6M`|bw3Wi79EjH+gQnPd~V{dQ*N#=J0k}s zKFj3c2N^F5Tf=g>>WIiYNcUV{em8 z`Od-=JcSIrf2G*Zj?xcr)!{nf!~w-2$=-a;yz;lM`lycZ=Nn(Yrr-0o5YFAP^u2S= z@9+s<%XIXX3&a1r=<`7Fr!;C{0q@64?{+xnMaavY-CT=bdm@&sEmOR(x~}S{my&xpSqmrCwE`-lk?x?kzQfOyxDt=&MKT?`Lrg1jOxo$V?@ZW-<3V>A zbQ8Q3i0hr;X&GFWIEzO8fQcNKMa1vprq@UqN~JJykqD~EfPE@hXK$&-MMlcYb1J2` zYPhTx#>T!5st3pjRV{1!Zb=xLaqfC;Vpa&JAWAcPXP+ubN6j+n%axDk8f`9ScLh?i zs2I-%STk<>m-eWWs{PKLJLe&yUqTE@19xfbxl@)_eZ%GAzQ=yB!?Cc2rt8<=ac57M zAli6E!h)x*`5!yX3x79&Srtq4m3R;x3jac#Dvg^y+a4l=UuVE6w@QhS=TVH%whReY zMKp}E%T(;090d`bNgTT<9OVkZoZ6A6n&w_Aw1>&Kli&lfA@(quBn4S@I)B6qS0S59 zM$V8VsP2`V)~h8s{jEc)fAEeIkfnX#*PQoE(XXg6la6}oEqgRCDkj*~#Zdg_Ripkv zBKLi~Ew&%sHOfmhtr~<(iR-?$z=QzbDadEtFunF7PAmGR&NKe?+m+i5wPsD>tRQ8dqCY9Vion`>&NRH=4Rb|OKsHSu2aP4UDR zQ|_kItWVD^<;hBKmX4jw|AfG$#C$F6vyKxu*vhC7CTOh-M=<4t0&+)Yy)*Bv~i6MDTX>Yb_-c_5;Glo((!I%=~}{{@%R zxK4D3;&A!cV9sO!H8GV|Xv(^K3%z|IBHAb~H@Jx@^t~H2vtt@)+RwQ)(em<{c2hAq z$g0iq^0#bLS_7>WIsugJ1KSmLVnV&f7?l~{QOQE!9l9~3H9S2b%N zje4!Xjxgbiyl?C3d0`l)3S5BC6W1CwnS^D!;vFrVJO7-cHk?t5i$-l?q{ zo2Fo~`uK@G_wDV^RC*JY=JST(uAvp|PcIWKj9V*4Nap$LIy5hApre{~_TUI1K2)lK z>$Z%hWp}EOqvfM1r?+X|f!-X$h18??gL0|u?CqP3L#3wJ51FP1WwS~cvU-FZx5j?_ z>6H<>ugfUAU-LG1MT}H%+`sg`nb_D^u7K!1&|9W^yh>#Q8A3u$iE%y!lm4B|{{vpA z&x&^=B}nmL2SBUg=_IBNU&b37C22Al{-GPlXs}hr=xEpnL>n%JB#!ObBmEZBgOCC` zOD3V{iKp%1&M(a2`1mLkAmkTx&F2ICR%7e$!Qjn~bO#+|#TBwOoh$VUmSjp1@8;?X z3&Os&>d3V=fG-TOd~U!x-ED1VESJ#6Rp%Qj?*(sXCIZ9)EE*^AfxY&N6{)8H1j1QA z@}&J4`aXbVe5HcIPy|jO@M}UZ(~rVL%!oy;i61d zp!vnTn=^-BPEU*JOX7hL<&?FGaACfFh&OevD>0MZls-I1U}FQ?MKtO{7uz|qq8WD) z+wG334Fku6#yuilpN^q*2oloFE3lK*j0U^uiC(~#kbZJ{M z6^QVC00sf_+`vq#)+rv>>Onb2XVQ;}gyQ^DukbqWCsdgG>NUUT&_@;Ca|GnCNUy!@S7r zXsw!%hEEqe14^DCH9=Km(Fd-ncjrUsa5@*OqXrqRAMXm}*ro8->Bf z=mtx4&b5(P3NHAP^|TH|QeG2(oIC|iBg;vEa^Sip-mKKsQwLn;qf6~RZ zsqk=Sof%UxRRNvP`E7jLc97;_NX|7^)VO|d&xeh8d|sC=^M^t>{C)&)ys`H_3LX(CB)9M4Ut9z-;y%{*@548hr&)L#NGuer`YG!Ee5L@ zGa`$nM4qj1hp!nOh;#;(Rx<=)fTpN20ZciWctNSzw{u|dbDG_6iwwqP47gh*S6;Lw zu*j}{hCDkCZ~2nu;b%)=iLN*~P~$$Jw?MM)BEcLFpcmLt17b2nya3#3KP{sN+^L={|0ZD9`vdVs;v{+%QnN4+oPrG2^S(iVyeD8A zY+_b8Ou^Y`!}0&KVbgGg{JDY0k}kh`*zGXr5m-g`0{lV+$>G%5%q+ickGY6z6M}(% zP#LxSOS{@pDA6Y1(6|!Q=jPhhSCbqzkgjM_niXW#FQF zq0Qk+e=_C^BKsELLpXykc)^|YsYHsoSpoI~76A6Nm;quhuzC0vi{Zn^gU$wEM9dI3 zHi@*mrME47@caSrEB3O5X?)UZ^yXPi2O0zn4Z6x=(~!f!_alcau;8OP=s!5?BWN=L ze3f<)eQG}NJfXd0{3Vq_jbPSN?J)Pbz?-dp3zY%RL&j*%6$l~AA_7aKM}orktutg< z5%d#zW+#ZAfM%3`PRagLiF*P|c#o?R$9;#$gkWP^J$j+Sq7NXRx^0UUWr7H+Uetr8 zK8b{lhS&;KDYj4xLZqNBml$ob6xri8kVQErH@zVDZ7wm#b#;_H+m>Gm-CVy9;dEF_ z+6B4GxVMPA)lt=MuI&+atEOI`3lharkZt0MYO-E#)Jv)tM!vj7Ua)}PUS`eTnD31m z>W;PwMFP_-RtEm@92uK=6zmSV86@9hc6)#v$YHLaa_Hn6d%5TsoZJJ`rS5N zQNi@RSM14c8m>8_%i#X1&wAALY+L>zBE6%z2NW1Rd_Cz!&CQ#@RwBWq6EwGZf`FY( zc7IwLLP5Cmq&;S Date: Sun, 25 Oct 2020 02:37:22 -0700 Subject: [PATCH 045/112] persist global buildingplan settings this ensures that a player's preferences are saved across map reloads. this is particularly important for the building material filters, since item fulfillment tasks are regenerated on map load, and any changes in settings when buildingplan is reset will change the item vectors that will be searched for planned buildings. if the settings were allowed to reset, then a player who thought all walls would be made out of blocks would be surprised to find boulders and logs being used after the map is reloaded. --- plugins/buildingplan-planner.cpp | 63 +++++++++++++++++++++++++++++++- plugins/buildingplan-planner.h | 1 + plugins/buildingplan.cpp | 36 +++++++++++++++++- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 42c84f4dc..963d13153 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -20,6 +20,7 @@ static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints"; static const std::string planned_building_persistence_key_v2 = "buildingplan/constraints2"; +static const std::string global_settings_persistence_key = "buildingplan/global"; /* * ItemFilter @@ -501,13 +502,69 @@ void migrateV1ToV2() } } -static void init_global_settings(std::map & settings) +// assumes no setting has '=' or '|' characters +static std::string serialize_settings(std::map & settings) +{ + std::ostringstream ser; + for (auto & entry : settings) + { + ser << entry.first << "=" << (entry.second ? "1" : "0") << "|"; + } + return ser.str(); +} + +static void deserialize_settings(std::map & settings, + std::string ser) +{ + std::vector tokens; + split_string(&tokens, ser, "|"); + for (auto token : tokens) + { + if (token.empty()) + continue; + + std::vector parts; + split_string(&parts, token, "="); + if (parts.size() != 2) + { + debug("invalid serialized setting format: '%s'", token.c_str()); + continue; + } + std::string key = parts[0]; + if (settings.count(key) == 0) + { + debug("unknown serialized setting: '%s", key.c_str()); + continue; + } + settings[key] = static_cast(atoi(parts[1].c_str())); + debug("deserialized setting: %s = %d", key.c_str(), settings[key]); + } +} + +static DFHack::PersistentDataItem init_global_settings( + std::map & settings) { settings.clear(); settings["blocks"] = true; settings["boulders"] = true; settings["logs"] = true; settings["bars"] = false; + + // load persistent global settings if they exist; otherwise create them + std::vector items; + DFHack::World::GetPersistentData(&items, global_settings_persistence_key); + if (items.size() == 1) + { + DFHack::PersistentDataItem & config = items[0]; + deserialize_settings(settings, config.val()); + return config; + } + + debug("initializing persistent global settings"); + DFHack::PersistentDataItem config = + DFHack::World::AddPersistentData(global_settings_persistence_key); + config.val() = serialize_settings(settings); + return config; } const std::map & Planner::getGlobalSettings() const @@ -525,6 +582,8 @@ bool Planner::setGlobalSetting(std::string name, bool value) debug("global setting '%s' %d -> %d", name.c_str(), global_settings[name], value); global_settings[name] = value; + if (config.isValid()) + config.val() = serialize_settings(global_settings); return true; } @@ -535,7 +594,7 @@ void Planner::reset() planned_buildings.clear(); tasks.clear(); - init_global_settings(global_settings); + config = init_global_settings(global_settings); migrateV1ToV2(); diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h index 18cfaf0b1..7b1615704 100644 --- a/plugins/buildingplan-planner.h +++ b/plugins/buildingplan-planner.h @@ -120,6 +120,7 @@ public: void doCycle(); private: + DFHack::PersistentDataItem config; std::map global_settings; std::unordered_map, diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 52bd5118f..a89c6c8fd 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -7,6 +7,7 @@ #include "modules/Maps.h" #include "modules/World.h" +#include "Core.h" #include "LuaTools.h" #include "PluginManager.h" @@ -814,6 +815,33 @@ IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render); +static DFHack::PersistentDataItem get_ui_settings_config() +{ + static const std::string settings_persistence_key = + "buildingplan/ui_settings"; + + bool added; + DFHack::PersistentDataItem config = + DFHack::World::GetPersistentData(settings_persistence_key, &added); + if (added) + { + config.ival(0) = quickfort_mode; + } + return config; +} + +static void load_settings() +{ + DFHack::PersistentDataItem config = get_ui_settings_config(); + quickfort_mode = config.ival(0); +} + +static void save_settings() +{ + DFHack::PersistentDataItem config = get_ui_settings_config(); + config.ival(0) = quickfort_mode; +} + static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { if (!parameters.empty()) @@ -841,7 +869,11 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { - planner.reset(); + if (DFHack::Core::getInstance().isMapLoaded()) + { + load_settings(); + planner.reset(); + } if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) || !INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) || @@ -871,6 +903,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { switch (event) { case SC_MAP_LOADED: + load_settings(); planner.reset(); roomMonitor.reset(out); break; @@ -928,6 +961,7 @@ static void setSetting(std::string name, bool value) { { debug("setting quickfort_mode %d -> %d", quickfort_mode, value); quickfort_mode = value; + save_settings(); return; } planner.setGlobalSetting(name, value); From 72b6ac781b2b5996b58a50ab872009fd1b39bf8e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 25 Oct 2020 02:45:03 -0700 Subject: [PATCH 046/112] on second thought, let UI-related settings reset no need to have quickfort_mode stay on forever --- plugins/buildingplan.cpp | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index a89c6c8fd..528bd86cd 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -815,33 +815,6 @@ IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render); -static DFHack::PersistentDataItem get_ui_settings_config() -{ - static const std::string settings_persistence_key = - "buildingplan/ui_settings"; - - bool added; - DFHack::PersistentDataItem config = - DFHack::World::GetPersistentData(settings_persistence_key, &added); - if (added) - { - config.ival(0) = quickfort_mode; - } - return config; -} - -static void load_settings() -{ - DFHack::PersistentDataItem config = get_ui_settings_config(); - quickfort_mode = config.ival(0); -} - -static void save_settings() -{ - DFHack::PersistentDataItem config = get_ui_settings_config(); - config.ival(0) = quickfort_mode; -} - static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { if (!parameters.empty()) @@ -870,10 +843,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) if (enable != is_enabled) { if (DFHack::Core::getInstance().isMapLoaded()) - { - load_settings(); planner.reset(); - } if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) || !INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) || @@ -903,7 +873,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { switch (event) { case SC_MAP_LOADED: - load_settings(); planner.reset(); roomMonitor.reset(out); break; @@ -961,7 +930,6 @@ static void setSetting(std::string name, bool value) { { debug("setting quickfort_mode %d -> %d", quickfort_mode, value); quickfort_mode = value; - save_settings(); return; } planner.setGlobalSetting(name, value); From ebba769a0c0df155346982958de3f74eca25a47f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 25 Oct 2020 09:37:45 -0700 Subject: [PATCH 047/112] blueprint library playtesting fixes - balance wings of The Saracen Crypts - add stairs to Whirlpool Housing - move burial script notes to message()s --- .../48-4-Raynard_Whirlpool_Housing.csv | 6 +- .../bedrooms/95-9-Hactar1_3_Branch_Tree.csv | 2 +- .../blueprints/library/tombs/Mini_Saracen.csv | 2 +- .../library/tombs/The_Saracen_Crypts.csv | 196 +++++++++--------- 4 files changed, 103 insertions(+), 103 deletions(-) diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv index 9329353f4..8906ae4ba 100644 --- a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv +++ b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv @@ -13,9 +13,9 @@ d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d,# , , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , , ,# , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,# ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, ,# - , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,# - , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , ,# - , ,d, , ,d,d,d, , ,d, , ,d,d,d,d,d, , ,d, , ,d,d,d, , ,d, , ,# + , ,d, , ,d,d,d, , ,d, , ,d,i,i,i,d, , ,d, , ,d,d,d, , ,d, , ,# + , ,d,d,d,d,d,d,d,d,d,d,d,d,i,i,i,d,d,d,d,d,d,d,d,d,d,d,d, , ,# + , ,d, , ,d,d,d, , ,d, , ,d,i,i,i,d, , ,d, , ,d,d,d, , ,d, , ,# ,d,d,d, ,d,d,d, ,d,d,d, , , ,d, , , ,d,d,d, ,d,d,d, ,d,d,d, ,# , ,d, , , ,d, , , ,d, , ,d, ,d, ,d, , ,d, , , ,d, , , ,d, , ,# , , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , , ,# diff --git a/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv index 37f41fd23..f9e97bc8d 100644 --- a/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv +++ b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv @@ -152,7 +152,7 @@ , , , , , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , , , , , ,# , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# #,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# -#query label(rooms) start(36;73) room designations (use burial script to mark urns as usable) +#query label(rooms) start(36;73) message(use burial script to mark urns as usable) room designations , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,r+,, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# diff --git a/data/blueprints/library/tombs/Mini_Saracen.csv b/data/blueprints/library/tombs/Mini_Saracen.csv index e32381b82..f30fbc55d 100644 --- a/data/blueprints/library/tombs/Mini_Saracen.csv +++ b/data/blueprints/library/tombs/Mini_Saracen.csv @@ -11,7 +11,7 @@ d, ,d, ,d,d,d, ,d, ,d,# d,d,d,d,d,d,d,d,d,d,d,# d, ,d, ,d, ,d, ,d, ,d,# #,#,#,#,#,#,#,#,#,#,#,# -#build label(urns) start(6;6) 24 urns (use burial script to mark them as usable) +#build label(urns) start(6;6) message(use burial script to mark urns as usable) 24 urns n, ,n, ,n, ,n, ,n, ,n,# `,`,`,`,`,`,`,`,`,`,`,# n, ,n, ,`,`,`, ,n, ,n,# diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.csv b/data/blueprints/library/tombs/The_Saracen_Crypts.csv index a4bd7fc95..ae6107894 100644 --- a/data/blueprints/library/tombs/The_Saracen_Crypts.csv +++ b/data/blueprints/library/tombs/The_Saracen_Crypts.csv @@ -1,98 +1,98 @@ -#dig start(24;24) room for 489 corpses -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,i,i,i,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# - , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , ,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# -d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# -#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# -#build label(urns) start(24;24) 489 urns (use burial script to mark them as usable) -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,~,~,~,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# - , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , ,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# -n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# -#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig start(24;24) room for 513 corpses +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,i,i,i,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,i,i,i,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , ,d,d,d, , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# + , , , , , , , , , , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , , , , , , , , , , ,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(urns) start(24;24) message(use burial script to mark urns as usable) 513 urns +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,~,~,~,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`,`,`,`,~,~,~,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , ,`,`,`, , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n,`,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# + , , , , , , , , , , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , , , , , , , , , , ,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`, , , , , , , , , ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,`,`,`,`,`,`,`,`,`,`, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n, ,n,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# From 0e2d29cd8d44ed4f069b60c302e19f0f3e76a924 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 25 Oct 2020 14:25:02 -0700 Subject: [PATCH 048/112] increase readability of lists in dreamfort help --- data/blueprints/library/dreamfort.csv | 160 +++++++++++++++----------- 1 file changed, 91 insertions(+), 69 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 096a6657e..1c0222477 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -29,28 +29,34 @@ You are welcome to copy those spreadsheets and make your own modifications! "Sets up a large, protected entrance to your fort in a flat area on the surface." "" Features: -A starting set of workshops and stockpiles (which you can later remove once you establish your permanent workshops and storage) -Trade depot area -"Walls, roof, and lever-controlled gates for security" -Trap-filled hallways for invaders -Livestock grazing area with nestbox zones and beehives -A grid of 1x1 farm plots (meant to be managed with DFHack autofarm) -An extra room near the entrance for a barracks (once you get your military going) -Miasma vents for the farming level that is intended to be built in the layer directly beneath the surface +- A starting set of workshops and stockpiles (which you can later remove once you establish your permanent workshops and storage) +- Trade depot area +"- Walls, roof, and lever-controlled gates for security" +- Trap-filled hallways for invaders +- Livestock grazing area with nestbox zones and beehives +- A grid of 1x1 farm plots (meant to be managed with DFHack autofarm) +- An extra room near the entrance for a barracks (once you get your military going) +- Miasma vents for the farming level that is intended to be built in the layer directly beneath the surface "" Manual steps you have to take: -Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox can manage the nestbox zones) -Connect levers to the gates +- Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox can manage the nestbox zones) +- Connect levers to the gates "" Be sure to choose an embark site that has an area flat enough to use these blueprints! "" Surface Walkthrough: "1) Choose a tile for your central staircase. The terrain around that tile should be perfectly flat. Trees are ok, but no slopes, rivers, or lakes. To be sure that the tile you've chosen is in a good spot, set the cursor over that tile and run ""quickfort run library/dreamfort.csv -n /surface4"". This will show you the eventual boundaries of the fort. Some wall segments might be missing due to existing trees, but that's ok. Make sure the area within the exterior wall is flat. Run ""quickfort undo library/dreamfort.csv -n /surface4"" to clean up." +"" "2) With the cursor on the chosen tile, run /surface1 to clear surrounding trees and set up your pastures. Remember to assign your dogs to the pasture around the staircase and your grazing animals to the large pasture. You can let your cats roam free." +"" "3) Once the trees have been cleared, run /surface2 to set up starting workshops/stockpiles and clear trees from a larger area. Run ""quickfort orders"" for /surface4, /surface5, and /surface6 to get a head start manufacturing items for those blueprints. If you want a consistent color for your walls and floors, remember to set the rock material in the buildingplan UI and in the manager orders for the blocks." +"" "4) Once the trees have been cleared from the larger area, run /surface3 to channel out the miasma vents for the farming level." +"" "5) When the channels have been dug, run /surface4 to build the protective walls and flooring. You can also start digging out the sub-surface farming level (/farming1) at this point. Although the vents will be covered with flooring, they will still work to prevent miasma." +"" "6) Once all the constructions are built, run /surface5 build gates, furniture, the trade depot, and traps." +"" "7) When at least the beehives are in place, run /surface6 to configure the hives and construct a roof." "#meta label(surface1) start(staircase center) message(Once the stairwell is mined out, you should start digging the industry level in a non-aquifer rock layer. @@ -866,32 +872,35 @@ p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p, "Sets up farming, food storage, and related industries. Also provides post-embark necessities that can later be disassembed." "" Features: -Pairs with the surface blueprints to provide miasma vents -Farm plots (intended to be managed by DFHack autofarm) -Plentiful food storage -Refuse stockpile and quantum dump for useless body parts -Small dormitory and dining room for post-embark needs -Small office for your manager +- Pairs with the surface blueprints to provide miasma vents +- Farm plots (intended to be managed by DFHack autofarm) +- Plentiful food storage +- Refuse stockpile and quantum dump for useless body parts +- Small dormitory and dining room for post-embark needs +- Small office for your manager "" Workshops: -Kitchen -Brewery -Butcher -Fishery -Tannery -Farmer's Workshop -Quern -Screw Press +- Kitchen +- Brewery +- Butcher +- Fishery +- Tannery +- Farmer's Workshop +- Quern +- Screw Press "" Manual steps you have to take: -Assign the office to your manager -Assign a minecart to your quantum garbage stockpile hauling route -"If the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level" +- Assign the office to your manager +- Assign a minecart to your quantum garbage stockpile hauling route +"- If the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level" "" Farming Walkthough: "1) Wait until you have completed /surface3 before digging out the farming level on the z-level below the surface, otherwise you will likely get caveins as your miners channel out the miasma vents over empty space." +"" "2) Start digging with /farming1 and get started on manufacturing furniture by running ""quickfort orders"" on /farming2." +"" "3) Once the level is dug out, run /farming2 to build workshops and build and configure stockpiles. Remember to assign a minecart to the newly-designated quantum garbage dump. There are also jugs, pots, and bags stockpiles on this level that should be configured to ""take"" from the industry level stockpiles once we get the industry level built." +"" "4) When the furniture is in place, run /farming3 to designate your temporary dining room and dormitory. The blueprint also attempts to assign the office to your manager, but double-check this assignment in case your dwarves are in an unexpected order." "#dig label(farming1) start(23; 25; staircase center) message(This would be a good time to queue up manager orders for /farming2. Once the area is dug out, continue with /farming2.)" @@ -1168,45 +1177,49 @@ query_stockpiles/farming_query_stockpiles Sets up workshops for all non-farming industries "" Features: -Quantum stockpiles -Space-efficient layout for all workshops +- Quantum stockpiles +- Space-efficient layout for all workshops with separate stockpiles for: -A reserve of uncut gems for strange moods that the jeweler's workshop cannot take from -Steel bars and coal so you can see at a glance if you're low on either -Liquids that cannot be quantum stockpiled (e.g. lye) -Meltable weapons and armor +- A reserve of uncut gems for strange moods that the jeweler's workshop cannot take from +- Steel bars and coal so you can see at a glance if you're low on either +- Liquids that cannot be quantum stockpiled (e.g. lye) +- Meltable weapons and armor "" Workshops: -3x Mason -4x Craftsdwarf -1x Jeweler -1x Mechanic -4x Smelter -1x Forge -1x Glassmaker -1x Kiln -4x Wood furnace -1x Ashery -1x Soap maker -1x Carpenter -1x Siege workshop -1x Bowyer -1x Dyer -1x Loom -1x Clothier +- 3x Mason +- 4x Craftsdwarf +- 1x Jeweler +- 1x Mechanic +- 4x Smelter +- 1x Forge +- 1x Glassmaker +- 1x Kiln +- 4x Wood furnace +- 1x Ashery +- 1x Soap maker +- 1x Carpenter +- 1x Siege workshop +- 1x Bowyer +- 1x Dyer +- 1x Loom +- 1x Clothier "" "" 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" -"If desired, set the stockpiles in the bottom right to auto-melt. This results in melting all weapons and armor that are not masterwork steel. This is great for your military, but it takes a *lot* of fuel. If you enable this, be sure you're in a heavily forested area, enable auto-chop, and set up manager orders to keep your coal stocks high." -Download automation.json from https://drive.google.com/file/d/17WcN5mK-rnADOm2B_JFpPnByYgkYjxhb/view and put it in your dfhack-config/orders/ directory. +- 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" +"- If desired, set the stockpiles in the bottom right to auto-melt. This results in melting all weapons and armor that are not masterwork steel. This is great for your military, but it takes a *lot* of fuel. If you enable this, be sure you're in a heavily forested area, enable auto-chop, and set up manager orders to keep your coal stocks high." +- Download automation.json from https://drive.google.com/file/d/17WcN5mK-rnADOm2B_JFpPnByYgkYjxhb/view and put it in your dfhack-config/orders/ directory. "" Industry Walkthrough: 1) You can start digging out /industry1 immediately after embark. It is best to choose a stone layer at least two layers underground so the boulders can be used by your starting workshops to manufacture needed items. +"" "2) Queue up manufacturing by running ""quickfort orders"" on /industry2." +"" "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 to the jugs, pots, and bags stockpiles on the farming level." +"" "4) If you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt." +"" "5) Run ""orders import automation"" to use the provided automation.json to take care of your fort's basic needs." "#dig label(industry1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /industry2. Once the area is dug out, continue with /industry2.)" @@ -1364,25 +1377,29 @@ query/industry_query "Sets up public services (dining, hospital, etc.)" "" Features: -Spacious dining room (also usable as a tavern) -Prepared food and drink stockpiles -Well system (bring your own water) -Public baths with soap stockpiles -Three well-appointed jail cells -Hospital with beds and storage -Garbage dump +- Spacious dining room (also usable as a tavern) +- Prepared food and drink stockpiles +- Well system (bring your own water) +- Public baths with soap stockpiles +- Three well-appointed jail cells +- Hospital with beds and storage +- Garbage dump "" Manual steps you have to take: -"If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." -"Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)" -"Activate the bath pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." -"Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water to prevent muddiness. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." +"- If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." +"- Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)" +"- Activate the bath pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way." +"- Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water to prevent muddiness. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." "" Services Walkthough: 1) Start this level before your fort grows beyond around 50 dwarves so everyone has a place to eat. +"" "2) Start digging with /services1. Note that this digs out the main level and three levels below for the well plumbing. Start manufacturing with ""quickfort orders"" on /services2." +"" "3) Once the area is dug out, set up the furniture, stockpiles, and hospital and garbage dump zones with /services2. Fill your soap stockpiles around the bath channels by configuring them to take from the bar quantum stockpile (the one on the right near the forge) on the industry level." +"" "4) When all furniture is placed, run /services3 to configure your dining room and jail." +"" 5) Fill the bath and wells with either a bucket brigade or by carefully routing flowing water to them. "#dig label(services1) start(23; 22; staircase center) message(This would be a good time to queue manager orders for /services2. Once the area is dug out, continue with /services2.)" @@ -1701,11 +1718,13 @@ query_stockpiles/services_query_stockpiles "Multiple 7x7 rooms for guildhalls, temples, libraries, etc." "" Features: -"Big empty rooms. Double-thick walls to ensure engravings add value to the ""correct"" side. Fill with furniture and assign as needed." +"- Big empty rooms. Double-thick walls to ensure engravings add value to the ""correct"" side. Fill with furniture and assign as needed." "" Guildhall Walkthrough: "1) Dig out the rooms with /guildhall1 and queue up manufacturing by running ""quickfort orders"" on /guildhall2." +"" "2) Once the area is dug out, add in generic furniture with /guildhall2." +"" "3) Furnish individual rooms as you need specific guildhalls, libraries, and temples." "#dig label(guildhall1) start(25; 25; staircase center) message(This would be a good time to queue manager orders for /guildhall2. Once the area is dug out, continue with /guildhall2.)" @@ -1811,17 +1830,20 @@ Guildhall Walkthrough: Suites for nobles and apartments for the teeming masses "" Features: -Well-appointed suites to satisfy most nobles -Apartments with beds and storage to keep dwarves happy -Meta blueprint included for designating 6 levels of apartments for a full 200+ dwarves +- Well-appointed suites to satisfy most nobles +- Apartments with beds and storage to keep dwarves happy +- Meta blueprint included for designating 6 levels of apartments for a full 200+ dwarves "" Suites Walkthrough: "1) Dig out the suites layer with /suites1 and queue up manufacturing by running ""quickfort orders"" on /suites2." +"" "2) Once the area is dug out, furnish the suites with /suites2. The rooms are left unconfigured so you can set them up as needed room types and assign them to specific nobles. Each room can serve as a bedroom, a dining hall, an office, or a tomb." "" Apartments Walkthrough: "1) Dig out one layer of apartments with /apartments1, or 6 layers at once (enough for 200 dwarves) with /apartments1_stack. Run ""quickfort orders"" for /apartments2 once for every apartments layer that you are digging." +"" "2) Once a layer is dug out, furnish it with /apartments2." +"" "3) Once the beds are in place (the other furniture can still be unbuilt), configure the rooms with /apartments3. Once the urns are all in place, run ""burial -pets"" to set them all to accept burials." "#dig label(suites1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /suites2. Once the area is dug out, continue with /suites2) noble suites" From 031f2a2febe605c6e086ec3f93f464b5d2756fae Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 26 Oct 2020 01:23:24 -0400 Subject: [PATCH 049/112] Reduce build matrix entries --- .github/workflows/build.yml | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f0bb79cda..869ca407e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,17 +15,11 @@ jobs: - 4.8 - 7 plugins: - - supported + - default include: - os: ubuntu-20.04 gcc: 10 - plugins: supported - - os: ubuntu-20.04 - gcc: 10 - plugins: dev - - os: ubuntu-20.04 - gcc: 10 - plugins: stonesense + plugins: all steps: - name: Set up Python 3 uses: actions/setup-python@v2 @@ -79,15 +73,14 @@ jobs: -G Ninja \ -DDFHACK_BUILD_ARCH=64 \ -DBUILD_TESTS:BOOL=ON \ - -DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'dev' }} \ - -DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'dev' }} \ - -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'stonesense' }} \ - -DBUILD_SUPPORTED:BOOL=${{ matrix.plugins == 'supported' }} \ + -DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'all' }} \ + -DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'all' }} \ + -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \ + -DBUILD_SUPPORTED:BOOL=1 \ -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" ninja -C build-ci install - name: Run tests id: run_tests - if: ${{ matrix.plugins == 'supported' }} run: | export TERM=dumb mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init From 292d40a6ba52af0032a48dcca34f405b725f58e6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 26 Oct 2020 16:11:34 -0700 Subject: [PATCH 050/112] change hotkeys for adjusting min and max quality to avoid 'q' and 'w' conflicts with the standard DF UI hotkeys for roller speed. min quality: 'qw' -> 'QW' max quality: 'QW' - 'AS' --- plugins/buildingplan.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 501594c4a..b4ec44df1 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -525,6 +525,10 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest if (input->count(interface_key::CUSTOM_P) || input->count(interface_key::CUSTOM_F) || input->count(interface_key::CUSTOM_D) || + input->count(interface_key::CUSTOM_Q) || + input->count(interface_key::CUSTOM_W) || + input->count(interface_key::CUSTOM_A) || + input->count(interface_key::CUSTOM_S) || input->count(interface_key::CUSTOM_M)) { show_help = true; @@ -559,13 +563,13 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest if (input->count(interface_key::CUSTOM_SHIFT_M)) Screen::show(dts::make_unique(*filter), plugin_self); - else if (input->count(interface_key::CUSTOM_Q)) + else if (input->count(interface_key::CUSTOM_SHIFT_Q)) filter->decMinQuality(); - else if (input->count(interface_key::CUSTOM_W)) + else if (input->count(interface_key::CUSTOM_SHIFT_W)) filter->incMinQuality(); - else if (input->count(interface_key::CUSTOM_SHIFT_Q)) + else if (input->count(interface_key::CUSTOM_SHIFT_A)) filter->decMaxQuality(); - else if (input->count(interface_key::CUSTOM_SHIFT_W)) + else if (input->count(interface_key::CUSTOM_SHIFT_S)) filter->incMaxQuality(); else if (input->count(interface_key::CUSTOM_SHIFT_D)) filter->toggleDecoratedOnly(); @@ -669,10 +673,10 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest OutputString(COLOR_WHITE, x, y, title.c_str(), true, left_margin + 1); OutputString(COLOR_WHITE, x, y, get_item_label(key, filter_idx).c_str(), true, left_margin); - OutputHotkeyString(x, y, "Min Quality: ", "qw"); + OutputHotkeyString(x, y, "Min Quality: ", "QW"); OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); - OutputHotkeyString(x, y, "Max Quality: ", "QW"); + OutputHotkeyString(x, y, "Max Quality: ", "AS"); OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin); From 371f68f0a3f5ea2bedab5429cb8137c0ebf4b85c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 26 Oct 2020 16:22:15 -0700 Subject: [PATCH 051/112] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index dbe296c85..27581c1f7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,6 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `buildingplan`: all buildings, furniture, and constructions are now supported (except for the few building types not supported by dfhack itself) - `buildingplan`: now respects building job_item filters when matching items - `buildingplan`: default filter setting for max quality changed from ``artifact`` to ``masterwork`` +- `buildingplan`: min quality adjustment hotkeys changed from 'qw' to 'QW' to avoid conflict with existing hotkeys for setting roller speed. max quality adjustment hotkeys changed from 'QW' to 'AS' to make room for the min quality hotkey changes. ## API - `buildingplan`: added Lua interface API From 1a69a9b4833ad2ec6259d582376bf7fadeed5ec5 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 26 Oct 2020 16:54:50 -0700 Subject: [PATCH 052/112] add more important checks for matching items stolen (with love) from advfort.lua --- plugins/buildingplan-planner.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index dcdcfb777..9d73ab4c4 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -704,6 +704,7 @@ static bool matchesFilters(df::item * item, df::job_item * job_item, const ItemFilter & item_filter) { + // check the properties that are not checked by Job::isSuitableItem() if (job_item->item_type > -1 && job_item->item_type != item->getType()) return false; @@ -711,6 +712,12 @@ static bool matchesFilters(df::item * item, job_item->item_subtype != item->getSubtype()) return false; + if (job_item->flags2.bits.building_material && !item->isBuildMat()) + return false; + + if (job_item->metal_ore > -1 && !item->isMetalOre(job_item->metal_ore)) + return false; + if (job_item->has_tool_use > df::tool_uses::NONE && !item->hasToolUse(job_item->has_tool_use)) return false; From c1af3e281719933148f1a8570ebc6c272bebfc79 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 26 Oct 2020 21:13:11 -0700 Subject: [PATCH 053/112] document what happens if no types are allowed --- plugins/lua/buildingplan.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index ac92082f3..aa5a71eba 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -194,6 +194,7 @@ function GlobalSettings:init() self:make_setting_label_token('Bars', 'CUSTOM_R', 'bars', 10), self:make_setting_value_token('bars'), '\n', ' Changes to these settings will be applied to newly-planned buildings.\n', + ' If no types are enabled above, then any building material is allowed.\n', '\n', self:make_setting_label_token('Legacy Quickfort Mode', 'CUSTOM_F', 'quickfort_mode', 23), From 5debc43def5878187c90f53d8d551fe26ddefb23 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 26 Oct 2020 21:18:22 -0700 Subject: [PATCH 054/112] add port of Python Quickfort's TheQuickFortress - some bugs fixed, especially in the query blueprints - repackaged into a single file for easier distribution - all blueprints given labels for addressing - meta blueprints added for blueprints that could be applied as a group --- data/blueprints/library/quickfortress.csv | 768 ++++++++++++++++++++++ 1 file changed, 768 insertions(+) create mode 100644 data/blueprints/library/quickfortress.csv diff --git a/data/blueprints/library/quickfortress.csv b/data/blueprints/library/quickfortress.csv new file mode 100644 index 000000000..9d11ec675 --- /dev/null +++ b/data/blueprints/library/quickfortress.csv @@ -0,0 +1,768 @@ +#notes label(help) +"This is Buketgeshud, or translated from Dwarvish, The Quick Fortress. It is a set of basic blueprints for quickfort, demonstrating its use in assembling an entire basic (if incomplete) fort." +"" +Buketgeshud is designed around a 30x20 footprint with a common 2x2 central staircase. Blueprints can be repeated in any direction to connect in a modular fashion with adjacent 30x20 areas. A fortresswide example recirculating waterfall/plumbing system is included as an overlay if you're feeling hardcore. +"" +Walkthrough: +1) Embark! +"" +2) Clear a 30 wide x 20 high region of trees on the surface. This should be uninterrupted flat ground with soil (so that we can place farms below). Deconstruct your wagon. +"" +"3) Run /surface1. You'll want to put the cursor in the middle of the 30x20 cleared area (14 right, 8 down from the top left corner). This digs out stairs on the surface, a farm/depot/workshop level below, as well as the beginnings of an entrance moat. The beginnings of a 3rd z-level are also dug out; don't build anything here if you'd like to put waterfall plumbing in later." +"" +"4) After /surface1 is dug out, run /surface2 (beginning from the same starting position as you used for /surface1). This puts down a basic set of workshops commonly needed soon after embark, a couple farm plots, and a depot. It also places and configures starting stockpiles." +"" +"5) If your embark site is near any enemies, run /surface3 to build walls and traps on the surface to protect against invaders." +"" +"6) Dig out the central shaft and tunnels for several z-levels below our surface/depot level. Place the cursor THREE Z-levels below the surface, where no digging has occurred yet, and run /basic1 for 6 z-levels down starting from that level." +"" +"7) Optionally run /basic2 to designate booze-only stockpiles around the central stairs on every z-level below the farming level. The stockpiles are configured to take booze from the level above, so be sure to apply /basic2 on the top level first and work your way down." +"" +"8) Run workshops, bedrooms, and storeroom blueprints on any desired Z-level along our central shaft." +"" +"9) If desired, add a fortresswide waterfall system, bathing your dwarves in tile after tile of lovely waterfall mist as they go about their day. Run /waterfall1 on the z-level immediately below your farm/depot level (you left that space empty, didn't you?) and run /plumbing1 on z-levels below that, down to the bottom of your fort. Each application of /plumbing1 will dig out two floors. On the bottommost level, the screw pumps that will be placed there require 2 floor tiles to sit on, so remove or refloor the 2 northern channel designations in the lower right corner on that z-level. You'll also need a reservior in the z-level below that (not included)." +"" +"10) After all levels are dug out, apply /plumbing2 on the *bottommost* level, just above the reservior. The blueprint will build screw pumps on that level and the level above. Repeat on every alternate level up to the level below where you applied /waterfall1." +"" +"11) Finally, apply /waterfall2 on the z-level where you applied /waterfall1. Route flowing water to the 2 tiles in lower right." +"#dig label(surface1) start(15;10; top left corner of central stairs) message(The 3rd z-level just digs stairs; if you want to install the waterfall plumbing system later, leave this 3rd level EMPTY for now and start the base proper below that; use /basic1 to dig out areas for future use below.) Surface and farm/depot levels" +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,h,h,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,h,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,h,,h,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,h,h,h,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,h,h,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,h,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,h,h,h,h,,`,# +`,,,,j,,,,,,,,,,j,j,,,,,,,`,h,,,,,,`,# +`,,,,j,,,,,,,,,,j,j,,,,,,,`,h,,h,h,h,h,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,h,h,h,h,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,h,,h,h,h,h,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,h,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,h,h,h,h,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,~,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,`,h,h,h,h,h,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +d,d,d,d,j,d,d,d,d,d,d,d,d,d,j,j,d,d,d,d,d,d,`,`,`,`,`,`,`,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,,,,d,d,d,d,d,`,,,,,,,`,# +`,,,,d,,,,,,,,d,,d,d,,d,,,,,`,,,,,,,`,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,`,,,,,,,`,# +`,,,,d,,,,d,d,d,d,,d,d,d,d,,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,,d,d,d,d,,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,,,d,d,,,d,d,,,,,,,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,,d,d,d,d,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,,d,d,d,d,,r,r,r,r,d,d,d,d,d,`,,,,,,,`,# +d,d,d,d,d,d,d,d,d,d,d,d,,~,~,~,~,d,d,d,d,d,`,,,,,,,`,# +`,`,`,`,j,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +d,,,,i,,,,,,,,,,i,i,,,,,,,,,,j,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,d,d,,,,,,,,,,,,,,,# +,,,,i,,,,,,,,,d,i,i,d,,,,,,,,,j,,,,,# +,,,,i,,,,,,,,,d,i,i,d,,,,,,,,,j,,,,,# +,,,,,,,,,,,,,,d,d,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,i,,,,,,,,,,j,j,,,,,,,,,,j,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(surface2) Build basic workshops and stockpiles +/surface2_build +/surface2_place +/surface2_query +/surface2_doors +"#build label(surface2_build) hidden() start(15;10; top left corner of central stairs) Populates the surface and farm/depot levels with farm plots, workshops and a depot" +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,~,# +`,,,,,,,,wu,,,wr,,,,,,,,,,,`,,,,,,,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,wn,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,,,,,,,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,`,,,,p(6x7),,,,,,`,`,p(6x7),,,,,,`,`,`,`,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,wl,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,`,`,`,,`,`,`,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,`,`,,,`,`,,,`,`,`,`,`,`,`,,,,,,,`,# +,wc,,,,wm,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,`,,,,,,,D,,,`,,,,,,,`,# +,wt,,,,wr,,`,,,,,`,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,`,,,,,,,,,,`,,,,,,,`,# +,,,,`,,,`,,,,,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#place label(surface2_place) hidden() start(15;10; top left corner of central stairs) Lay stockpiles on surface and depot/farm levels +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,r(6x6),,,,,~,,,,,,,r(3x6),,,r(5x6),,,,,z(1x6),`,`,,,,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,`,`,`,# +`,~,,,,,~,,,,,,,~,,,~,,,,,~,`,`,,`,`,,,`,# +`,,,,,,,,,,,,,,,,,,w(4x8),,,,`,`,,,,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,u(11x3),,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,`,,,,,`,y(4x2),,,,~,~,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,`,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +f,f,f,f,,f,f,f,,,,,,,`,`,,,,,,,`,`,`,`,`,`,`,`,# +f,f,f,f,f,f,f,f,,,,,,,f(2x6),,,,,,,,`,,,,,,,`,# +f,f,f,f,f,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,,,,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,,,,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,,,,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +f,f,f,f,f,f,f,f,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,`,`,`,`,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,`,,f(2x1),,,`,,,,,`,,,,,,,`,# +w(4x2),,,,,f(9x2),,,,,,,,,`,`,f(1x2),,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,`,,,,g(4x2),,,,,,f(2x1),,,,,,,,`,,,,,,,`,# +,,,s(1x8),,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,,,,,g(4x5),,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,,`,`,`,`,,,,,,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(surface2_query) hidden() start(15;10; top left corner of central stairs) message(remember to set the farm plots to grow plump helmets) Adjust surface/depot level stockpiles +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,bodyparts,,,,,~,,,,,,,rawhides,,,craftrefuse,,,,,,`,`,,,,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,`,`,`,`,# +`,~,,,,,~,,,,,,,~,,,~,,,,,~,`,`,,`,`,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,,`,`,`,`,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,,,,,,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,~,,,,,,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,`,`,`,`,`,`,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +noseeds,,,,,,,,,,,,,,`,`,,,,,,,`,`,`,`,`,`,`,`,# +,,,,,,,,,,,,,,seeds,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,`,,,,,,`,`,`,`,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,`,,booze,t{Down}{Left 2}&,,`,,,,,`,,,,,,,`,# +,,,,,booze,,,,,,,,,`,`,booze,,,,,,`,,,,,,,`,# +,,,,,t{Up 5}&,,,,,,,,,`,`,t{Left 3}&,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,,,booze,t{Up}{Left 2}&,,,,,,,`,,,,,,,`,# +,,,otherstone,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,~,~,~,~,,,,,,`,,,,,,,`,# +,,,,`,,,,,,,,,`,`,`,`,,,,,,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(surface2_doors) hidden() start(15;10; top left corner of central stairs) Just builds doors on the depot level (just below the surface) +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +`,,,,d,,,,,,,,d,,,,,d,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,`,`,,,,,,,`,,,,,,,`,# +`,,,,d,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,d,d,,,d,d,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,d,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,d,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,`,`,`,`,,,,,,`,,,,,,,`,# +,,,,,,,,,,,,,~,~,~,~,,,,,,`,,,,,,,`,# +`,`,`,`,~,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(surface3) Build walls and traps to protect against invaders +/surface3_walls +/surface3_traps +#build label(surface3_walls) hidden() start(15;10; top left corner of central stairs) Builds walls and bridges on the surface level. Note that the entrance on the southern wall juts out from the 30x20 footprint by 3 tiles; the southern bridge extends beyond the edge of the blueprint itself.\n\nYou'll need to add and connect levers yourself. +Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,gd(2x3),,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,Cw,Cw,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,,,,,,,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,gw(4x2),,,,Cw,,,,,Cw,,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,,,,,Cw,,,,,Cw,Cw,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,,,,,Cw,,,,,ga(2x1),,,,,,,Cw,# +Cw,,,,,,,,,,,,Cw,,,,,Cw,,,,,Cw,Cw,,,,,,Cw,# +Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,,,,,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,Cw,# +,,,,,,,,,,,,Cw,,,,,Cw,,,,,,,,,,,,,# +,,,,,,,,,,,,Cw,gw(4x2),,,,Cw,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(surface3_traps) hidden() start(15;10; top left corner of central stairs) Put some stone-fall traps down. +`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,`,,~,~,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,Ts,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,`,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,`,`,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,,,,,,,,,,,`,`,Ts,`,`,`,`,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,`,~,~,~,~,`,,,,,`,`,`,`,`,`,Ts,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,~,~,Ts,Ts,Ts,Ts,Ts,`,# +`,,,,,,,,,,,,`,,,,,`,,,,,`,`,`,`,`,`,`,`,# +`,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(basic1) start(15;10; top left corner of central stairs) Common stair/shaft digging for all floors below surface/depot levels +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(basic2) Place and configure food/booze stockpiles around the central staircase +/basic2_place +/basic2_query +#place label(basic2_place) hidden() start(15;10; top left corner of central stairs) Places food stockpiles around the central staircase +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,f(2x1),,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,f(1x2),`,`,f(1x2),,,,,,,,,`,,,,,# +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,f(2x1),,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#query label(basic2_query) hidden() start(15;10; top left corner of central stairs) configures booze stockpiles around stairway, taking from the stockpile on the level above" +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,booze,t<&,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,booze,`,`,booze,,,,,,,,,`,,,,,# +,,,,`,,,,,,,,,t<&,`,`,t<&,,,,,,,,,`,,,,,# +,,,,,,,,,,,,,,booze,t<&,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,`,,,,,,,,,,`,`,,,,,,,,,,`,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#dig label(workshops1) start(15;10; top left corner of central stairs) Just four big rooms, suitable for workshops" +d,d,d,d,i,d,d,d,d,d,d,d,d,,i,i,,d,d,d,d,d,d,d,d,i,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,# +,d,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +,d,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,`,`,`,`,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,`,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,`,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,`,,,,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,`,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(workshops2) Build commonly needed workshops and associated stockpiles +/workshops2_build +/workshops2_place +/workshops2_doors +#build label(workshops2_build) hidden() start(15;10; top left corner of central stairs) Sufficient workshops for basic non-food needs +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,wj,,,,we,,,,we,,,`,`,,,es,,,,ew,,,,ek,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,wj,,,,we,,,,we,,,`,`,,,es,,,,eg,,,,wf,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,wr,,,,wr,,,,wm,,,`,`,,,wc,,,,wc,,,,wb,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,wt,,,,wt,,,,wm,,,`,`,,,wc,,,,wc,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#place label(workshops2_place) hidden() start(15;10; top left corner of central stairs) Workshop source material piles placed around the workshops. +e,e,e,e,,l,l,l,l,l,l,l,l,,`,`,,b,b,b,b,b,b,b,b,,b,b,b,,# +e,e,e,e,e,l,l,l,l,l,l,l,l,,`,`,,b,b,b,b,b,b,b,b,b,b,b,b,,# +e,e,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +e,e,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +e,e,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,l,,,,l,,,,,`,`,,,,,b,,,,b,,,,b,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +`,`,`,`,~,`,`,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,`,`,~,`,`,`,`,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,s(10x1),,,,,,,,,,,`,`,,w,w,w,w,w,w,w,w,w,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,w,,,,w,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(workshops2_doors) hidden() start(15;10; top left corner of central stairs) Fill in doors to the workrooms. +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,d,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,d,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#dig label(storeroom1) start(15;10; Top left corner of central stairs) Just four big rooms, suitable for storerooms" +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +,,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +,,,,d,,,,,,,,d,,d,d,,d,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,d,d,d,d,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,,d,d,,d,d,d,d,d,d,d,d,d,,,,,# +d,d,d,d,d,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,d,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#meta label(storeroom2a) General stockpiles +/storeroom2a_place +/storeroom2_doors +"#meta label(storeroom2b) Extra storage for wood, food and furniture" +/storeroom2b_place +/storeroom2_doors +#place label(storeroom2a_place) hidden() start(15;10; top left corner of central stairs) General stockpiles +g(6x8),,,,x(1x1),,l(7x8),,,,,,,,,,,d(7x5),,,,,,,p(6x5),x(1x1),,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,b(7x3),,,,,,,z(6x3),,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +u(13x8),,,,,,,,,,,,,,,,,u(7x8),,,,,,,w(6x8),,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,x(4x5),,,`,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,`,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#place label(storeroom2b_place) hidden() start(15;10; top left corner of central stairs) Extra storage for wood, food and furniture" +w(13x8),,,,x(1x1),,,,,,,,,,,,,f(13x8),,,,,,,,x(1x1),,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +`,`,`,`,,`,`,`,`,`,`,`,,`,,,`,,`,`,`,`,`,`,`,,`,`,`,`,# +u(13x8),,,,,,,,,,,,,,,,,u(13x8),,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,x(4x5),,,`,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,`,,,`,,,,,,,,,,`,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,`,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(storeroom2_doors) hidden() start(15;10; top left corner of central stairs) Build storeroom doors +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,d,,,,,,,,d,,,,,d,,,,,,,,d,,,,d,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,d,,,d,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(bedrooms1) start(15;10; top left corner of central stairs) Bedroom complex +d,d,d,,i,,d,d,d,,d,d,d,,i,i,,d,d,d,,d,d,d,,i,,,,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,,d,d,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,d,d,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,d,d,,# +d,,d,,d,,d,,d,,,,,,d,d,,,,,,d,,d,,d,,d,d,,# +d,,d,,d,,d,,d,,,d,d,d,d,d,d,d,d,,,d,,d,,d,d,d,d,,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,,,,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,d,d,d,i,d,d,d,d,d,d,d,d,d,i,i,d,d,d,d,d,d,d,d,d,i,d,d,d,d,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,,,,# +d,,d,,d,,d,,d,,,d,d,d,d,d,d,d,d,,,d,,d,,d,d,d,d,,# +d,,d,,d,,d,,d,,,,,,d,d,,,,,,d,,d,,d,,d,d,,# +d,,d,,d,,d,,d,,d,d,d,d,d,d,d,d,d,d,,d,,d,,d,,d,d,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,,,,,# +,,,,d,,,,,,,,,,d,d,,,,,,,,,,d,,,,,# +d,d,d,d,d,d,d,d,d,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,,,,,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#build label(bedrooms2) start(15;10; top left corner of central stairs) Bedroom furniture +f,h,h,,,,h,h,f,,f,h,h,,,,,h,h,f,,f,h,h,,,,,,,# +b,,,d,,d,,,b,,b,,,d,,,d,,,b,,b,,,d,,,f,h,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,h,,# +b,f,h,d,,d,h,f,b,,b,f,h,d,,,d,h,f,b,,b,f,h,d,,d,,b,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +b,,b,,,,b,,b,,b,f,h,d,,,d,h,f,b,,b,,b,,,,f,h,,# +f,,f,,,,f,,f,,,,,,,,,,,,,f,,f,,,,,h,,# +h,,h,,,,h,,h,,,t,t,,,,,,,,,h,,h,,,d,,b,,# +d,,d,,,,d,,d,,,c,c,,,,,,,,,d,,d,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +d,,d,,,,d,,d,,,,,,,,,c,c,,,d,,d,,,,,,,# +h,,h,,,,h,,h,,,,,,,,,t,t,,,h,,h,,,d,,h,,# +f,,f,,,,f,,f,,,,,,,,,,,,,f,,f,,,,,h,,# +b,,b,,,,b,,b,,b,f,h,d,,,d,h,f,b,,b,,b,,,,f,b,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +b,f,h,d,,d,h,f,b,,b,f,h,d,,,d,h,f,b,,b,f,h,d,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +b,f,h,d,,d,h,f,b,,b,f,h,d,,,d,h,f,b,,b,f,h,d,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#query label(bedrooms3) start(15;10; top left corner of central stairs) Makes bedrooms and small dining rooms from beds and tables +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,r+,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,r+,,,,r+,,r+,,r+,,,,,,,,,r+,,r+,,r+,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,r++&,,,,,,,,,,,,,,,,,r+,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,r++&,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,r+,,,,r+,,r+,,r+,,,,,,,,,r+,,r+,,r+,,,,,r+,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +r+,,,,,,,,r+,,r+,,,,,,,,,r+,,r+,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(plumbing1) start(15;10; top left corner of central stairs) Plumbing for the waterfall system +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,h,h,# +,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,d,,h,h,# +,,,,,,,,,,,,,,,,,,,,,,,,,i,d,,d,d,# +#>,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,h,,,,,,,,,h,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,h,h,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,h,h,# +,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,i,d,,h,h,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(plumbing2) start(15;10; top left corner of central stairs) Grates, doors, and screw pumps for the waterfall plumbing" +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,`,`,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,d,Msm,Msm,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,~,~,# +#<,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,G,,,,,,,,,G,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,G,G,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,~,~,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,d,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,Msu,Msu,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,`,`,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +#dig label(waterfall1) start(15;10; top left corner of central stairs) Top-level plumbing for the waterfall system +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,,,d,# +,,,,,,d,,,,,,,,d,d,d,d,d,d,d,d,d,d,d,d,d,d,,d,# +,,,,,,d,,,,,,,,h,h,,,,,,,,,,,,d,,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,d,,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,d,,d,# +,,,,,,d,,,,,,,,,,,,,,,,,,,,,d,,d,# +,,,,i,,d,d,d,d,h,,,,i,i,,,,h,d,d,d,d,,i,,d,,d,# +,,,,i,,d,d,d,d,h,,,,i,i,,,,h,d,d,d,d,,i,,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,d,,,,,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,d,d,d,# +,,,,,,,,,,,,,,h,h,,,,d,d,d,d,d,d,d,d,d,d,d,# +,,,,,,,,,,,,,,d,d,,,,d,,,,,,,,,d,d,# +,,,,,,,,,,,,,,d,d,d,d,d,d,,d,d,d,,d,d,d,d,d,# +,,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,,,d,d,# +,,,,,,,,,,,,,,d,d,d,d,d,d,d,d,d,d,,d,d,d,d,d,# +,,,,i,,,,,,,,,,i,i,,,,,,,,,,i,,,d,d,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# +"#build label(waterfall2) start(15;10; top left corner of central stairs) message(Remember to link the levers and lock the doors manually) Floodgates, screw pumps, bridges and levers to control flow" +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,x,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,x,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,`,`,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,x,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,gw(2x1),,# +,,,,,,,,,,,,,,,,x,,,,,,Tl,,,,,d,,,# +,,,,,,,,,,,,,,,,,,,,,Tl,Tl,Tl,d,,,,,,# +,,,,,,,,,,,,,,,,,,,,,,Tl,,,,,d,Msm,Msm,# +,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# +#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,# From e61839b571f06ee73eb907cf6229e3d4e6ade721 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 28 Oct 2020 00:01:51 -0400 Subject: [PATCH 055/112] Fix ordered list --- docs/guides/quickfort-user-guide.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 9e5db34c3..964776f65 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -527,7 +527,7 @@ The valid hospital settings (and their maximum values) are: powder (15000) buckets (100) soap (15000) - + To toggle the ``active`` flag for zones, add an ``a`` character to the string. For example, to create a *disabled* pond zone (that you later intend to carefully fill with 3-depth water for a dwarven bathtub): @@ -1386,9 +1386,9 @@ There are also good examples in the query blueprint for how to use the Most of the baseline aliases distributed with DFHack fall into one of three categories: -# Make a stockpile accept only a particular item type in a category -# Permit an item type, but do not otherwise change the stockpile configuration -# Forbid an item type, but do not otherwise change the stockpile configuration +1. Make a stockpile accept only a particular item type in a category +2. Permit an item type, but do not otherwise change the stockpile configuration +3. Forbid an item type, but do not otherwise change the stockpile configuration If you have a stockpile that covers multiple tiles, it might seem natural to put one alias per spreadsheet cell. The aliases still all get applied to the From ccfc1f8a53bf5454bf0bfe9386a1f8433beddf6e Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 28 Oct 2020 00:19:26 -0400 Subject: [PATCH 056/112] Use keyboard formatting --- docs/Plugins.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 56dc785ff..73c0e1073 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -722,10 +722,11 @@ While placing a building, you can set filters for what materials you want the bu out of, what quality you want the component items to be, and whether you want the items to be decorated. -If a building type takes more than one item to construct, use Ctrl+Left and Ctrl+Right to -select the item that you want to set filters for. Any filters that you set will be used for -all buildings of the selected type from that point onward (until you set a new filter or -clear the current one). +If a building type takes more than one item to construct, use +:kbd:`Ctrl`:kbd:`Left` and :kbd:`Ctrl`:kbd:`Right` to select the item that you +want to set filters for. Any filters that you set will be used for all buildings +of the selected type from that point onward (until you set a new filter or clear +the current one). 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. From cf95c0ee2351fc6cc714a0e0719178f1f7c7ddda Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 28 Oct 2020 00:29:32 -0400 Subject: [PATCH 057/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index a574158ca..3716b2f8d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a574158ca3f12ed2911e77632e09972be56beb3a +Subproject commit 3716b2f8dd18bbfb5141ecefb5debed574178447 From 556c935e2b47d8ee93b3485e2c36df379f3ef08f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Oct 2020 22:01:27 -0700 Subject: [PATCH 058/112] update changelog --- docs/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 7f289c36d..cec1100c6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,6 +36,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences +## Misc Improvements +- `quickfort`: More blueprints have been added to the blueprints library: several bedroom layouts, the Saracen Crypts, and the complete fortress example from Python Quickfort: TheQuickFortress + # 0.47.04-r3 ## New Plugins From 5244ca106b0c28ba32950c4eb117a21041ab44a7 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Oct 2020 22:03:14 -0700 Subject: [PATCH 059/112] attribute dreamfort updates to the quickfort script --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 37cbe6b94..75106bec4 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: - `buildingplan`: all buildings, furniture, and constructions are now supported (except for the few building types not supported by dfhack itself) - `buildingplan`: now respects building job_item filters when matching items - `buildingplan`: default filter setting for max quality changed from ``artifact`` to ``masterwork`` -- The Dreamfort sample blueprints now have complete walkthroughs for each fort level and importable orders that automate basic fort stock management +- `quickfort`: The Dreamfort sample blueprints now have complete walkthroughs for each fort level and importable orders that automate basic fort stock management ## API - `buildingplan`: added Lua interface API From 08bb6ca35edfc38348afc6f29cf13e55fcc85428 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 29 Oct 2020 00:02:59 -0400 Subject: [PATCH 060/112] Tweak colors Ref #1684 --- plugins/lua/buildingplan.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index aa5a71eba..73fe160fb 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -129,19 +129,25 @@ function GlobalSettings:get_setting_string(name) return 'Off' end +function GlobalSettings:get_setting_pen(name) + if self.settings[name] then return COLOR_LIGHTGREEN end + return COLOR_LIGHTRED +end + function GlobalSettings:is_setting_enabled(name) return self.settings[name] end function GlobalSettings:make_setting_label_token(text, key, name, width) - return {text=text, key=key, key_sep=': ', key_pen=COLOR_GREEN, + return {text=text, key=key, key_sep=': ', key_pen=COLOR_LIGHTGREEN, on_activate=self:callback('toggle_setting', name), width=width} end function GlobalSettings:make_setting_value_token(name) return {text=self:callback('get_setting_string', name), enabled=self:callback('is_setting_enabled', name), - pen=COLOR_YELLOW, dpen=COLOR_GRAY} + pen=self:callback('get_setting_pen', name), + dpen=COLOR_GRAY} end -- mockup: From 9344e41e0a5dbe80875cd116b2e6d5027b4f204c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 29 Oct 2020 00:03:22 -0400 Subject: [PATCH 061/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 3716b2f8d..915f88055 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3716b2f8dd18bbfb5141ecefb5debed574178447 +Subproject commit 915f880558a2ed5775beaa5cecdbf2825483aef9 From 22ac163d55a0b83c9b360a8e916cc1833bf59050 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 29 Oct 2020 11:00:49 -0700 Subject: [PATCH 062/112] improve UX between automaterial and buildingplan solves the confusing behavior when both automaterial and buildingplan are enabled for constructions. the two plugins now communicate with each other over the Lua layer to negotiate consistent behavior. if neither plugin is enabled, the standard DF UI acts as normal if automaterial is enabled but buildingplan is not, then automaterial behavior is unchanged. if buildingplan is enabled and automaterial is not then behavior is the same as other buildings with buildingplan (no material selection screen, screen stays on building placement screen after placement). this commit fixes a bug, though, where buildingplan would only lay down a single tile of contruction instead of a solid block when a block is requested. if both plugins are enabled but buildingplan is not enabled for the building type then automaterial is unchanged from previous behavior, execpt for an additional header showing the separation between automaterial hotkeys and buildingplan hotkeys. finally, if both plugins are enabled and buildingplan is enabled for the building type then buildingplan behavior prevails, but the box select and hollow designations features of automaterial are still usable and useful. the 'Auto Mat-select', 'Reselect Type', and "Open Placement" automaterial hotkeys are hidden in the UI and ignored in the feed. This is because buildingplan takes over material selection, so 'Auto Mat-select' doesn't make sense. Buildingplan also already stays on the placement screen after placement, so 'Reselect Type' is not necessary. And all buildingplan-placed buildings have relaxed placement restrictions (e.g. they can be built in mid-air) so 'Open Placement' is also not necessary. The missing options are replaced with blank lines so the vertical alignment of all other options stays constant. we also remove a few extra lua_pop() calls that are made superfluous by the StackUnwinder. --- docs/Lua API.rst | 5 +- plugins/CMakeLists.txt | 2 +- plugins/automaterial.cpp | 186 ++++++++++++++++++++++++++++++----- plugins/buildingplan.cpp | 69 ++++++++++--- plugins/lua/automaterial.lua | 23 +++++ plugins/lua/buildingplan.lua | 53 +++++++--- 6 files changed, 278 insertions(+), 60 deletions(-) create mode 100644 plugins/lua/automaterial.lua diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 4c6f0d6fa..e69b13500 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3784,8 +3784,9 @@ buildingplan Native functions provided by the `buildingplan` plugin: -* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan -* ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list +* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan. +* ``bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the buildingplan UI is enabled for the specified building type. +* ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list. * ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. * ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 46a7d4097..9f5b2c679 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -90,7 +90,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static) dfhack_plugin(autohauler autohauler.cpp) dfhack_plugin(autolabor autolabor.cpp) - dfhack_plugin(automaterial automaterial.cpp) + dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) dfhack_plugin(automelt automelt.cpp) dfhack_plugin(autotrade autotrade.cpp) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 40dc2bb48..a238216c8 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -5,6 +5,7 @@ #include #include "Core.h" +#include "LuaTools.h" #include #include #include @@ -504,7 +505,7 @@ static bool find_anchor_in_spiral(const df::coord &start) return found; } -static bool find_valid_building_sites(bool in_future_placement_mode) +static bool find_valid_building_sites(bool in_future_placement_mode, bool use_buildingplan) { valid_building_sites.clear(); open_air_sites.clear(); @@ -519,7 +520,8 @@ static bool find_valid_building_sites(bool in_future_placement_mode) continue; building_site site(df::coord(xB, yB, box_second.z), false); - if (is_valid_building_site(site, false, true, in_future_placement_mode)) + // if we're using buildingplan, it will take care of filtering out bad tiles + if (use_buildingplan || is_valid_building_site(site, false, true, in_future_placement_mode)) valid_building_sites.push_back(site); else if (site.in_open_air) { @@ -531,25 +533,115 @@ static bool find_valid_building_sites(bool in_future_placement_mode) } } - size_t last_open_air_count = 0; - while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) + if (!use_buildingplan) { - last_open_air_count = open_air_sites.size(); - deque current_open_air_list = open_air_sites; - open_air_sites.clear(); - for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) + size_t last_open_air_count = 0; + while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) { - if (is_orthogonal_to_pending_construction(*it)) - valid_building_sites.push_back(*it); - else - open_air_sites.push_back(*it); - } + last_open_air_count = open_air_sites.size(); + deque current_open_air_list = open_air_sites; + open_air_sites.clear(); + for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) + { + if (is_orthogonal_to_pending_construction(*it)) + valid_building_sites.push_back(*it); + else + open_air_sites.push_back(*it); + } + } } return valid_building_sites.size() > 0; } +static bool is_buildingplan_enabled() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!(lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.buildingplan", "isEnabled") && + Lua::SafeCall(out, L, 0, 1))) + { + return false; + } + + return lua_toboolean(L, -1); +} + +static bool is_buildingplan_planmode_enabled( + df::building_type type, int16_t subtype, int32_t custom) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 4) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "isPlanModeEnabled")) + return false; + + Lua::Push(L, type); + Lua::Push(L, subtype); + Lua::Push(L, custom); + + if (!Lua::SafeCall(out, L, 3, 1)) + return false; + + return lua_toboolean(L, -1); +} + +static bool is_buildingplan_managed() +{ + return is_buildingplan_enabled() && + is_buildingplan_planmode_enabled(ui_build_selector->building_type, + ui_build_selector->building_subtype, + ui_build_selector->custom_type); +} + +static bool build_with_buildingplan_box_select(const df::coord &pos) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic( + out, L, "plugins.automaterial", + "build_with_buildingplan_box_select")) + { + return false; + } + + Lua::Push(L, ui_build_selector->building_subtype); + Lua::Push(L, pos.x); + Lua::Push(L, pos.y); + Lua::Push(L, pos.z); + + if (!Lua::SafeCall(out, L, 4, 1)) + return false; + + return lua_toboolean(L, -1); +} + +static bool build_with_buildingplan_ui() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + + CoreSuspendClaimer suspend; + Lua::StackUnwinder top(L); + + return lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.automaterial", + "build_with_buildingplan_ui") && + Lua::SafeCall(out, L, 0, 1); +} + static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item) { auto newinst = Buildings::allocInstance(pos, building_type::Construction, type); @@ -722,11 +814,13 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } else if (in_placement_stage()) { - if (input->count(interface_key::CUSTOM_A)) + bool use_buildingplan = is_buildingplan_managed(); + + if (!use_buildingplan && input->count(interface_key::CUSTOM_A)) { auto_choose_materials = !auto_choose_materials; } - else if (input->count(interface_key::CUSTOM_T)) + else if (!use_buildingplan && input->count(interface_key::CUSTOM_T)) { revert_to_last_used_type = !revert_to_last_used_type; } @@ -739,7 +833,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } - else if (input->count(interface_key::CUSTOM_O)) + else if (!use_buildingplan && input->count(interface_key::CUSTOM_O)) { allow_future_placement = !allow_future_placement; } @@ -816,6 +910,15 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } } + else if (use_buildingplan + && ui_build_selector->errors.size() == 0 + && input->count(interface_key::SELECT)) + { + build_with_buildingplan_ui(); + Gui::refreshSidebar(); + input->clear(); + return; + } } } //END UI Methods @@ -876,16 +979,17 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest static bool saved_revert_setting = false; static bool auto_select_applied = false; + bool use_buildingplan = is_buildingplan_managed(); box_select_mode = SELECT_MATERIALS; if (new_start) { bool ok_to_continue = false; bool in_future_placement_mode = false; - if (!find_valid_building_sites(false)) + if (!find_valid_building_sites(false, use_buildingplan)) { if (allow_future_placement) { - in_future_placement_mode = find_valid_building_sites(true); + in_future_placement_mode = find_valid_building_sites(true, use_buildingplan); } } else @@ -893,7 +997,8 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest ok_to_continue = true; } - if (in_future_placement_mode) + // if using buildingplan, we don't need an anchor + if (!use_buildingplan && in_future_placement_mode) { ok_to_continue = find_anchor_in_spiral(valid_building_sites[0].pos); } @@ -924,6 +1029,15 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { building_site site = valid_building_sites.front(); valid_building_sites.pop_front(); + + if (use_buildingplan) + { + // we don't actually care if this fails. buildingplan will return + // false when it filters out bad tiles, and that's ok. + build_with_buildingplan_box_select(site.pos); + continue; + } + if (box_select_materials.size() > 0) { df::construction_type type = (df::construction_type) ui_build_selector->building_subtype; @@ -986,8 +1100,10 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest // Allocation done, reset move_cursor(box_second); + // if we're using buildingplan, we nevef actually leave the placement + // screen, so there's no need to re-enter the screen revert_to_last_used_type = saved_revert_setting; - if (!revert_to_last_used_type) + if (!use_buildingplan && !revert_to_last_used_type) { send_key(df::interface_key::LEAVESCREEN); } @@ -1091,9 +1207,18 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } else if (in_placement_stage() && ui_build_selector->building_subtype < 7) { - OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); - AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); - AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); + bool use_buildingplan = is_buildingplan_managed(); + + OutputString(COLOR_BROWN, x, y, "DFHack Automaterial Options", true, left_margin); + if (use_buildingplan) + { + y += 2; + } + else + { + AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); + AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); + } ++y; AMOutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); @@ -1101,9 +1226,20 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { AMOutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); - AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); + + if (use_buildingplan) + ++y; + else + AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); } - ++y; + else + { + y += 3; + } + y += 2; + if (is_buildingplan_enabled()) + OutputString(COLOR_BROWN, x, y, "DFHack Buildingplan Options", true, left_margin); + if (box_select_enabled) { Screen::Pen pen(' ',COLOR_BLACK); diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ad0bdf358..ecd07a9e9 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -309,7 +309,6 @@ static std::string get_item_label(const BuildingTypeKey &key, int item_idx) if (!s) return "No string"; - lua_pop(L, 1); return s; } @@ -323,22 +322,27 @@ static bool construct_planned_building() if (!(lua_checkstack(L, 1) && Lua::PushModulePublic(out, L, "plugins.buildingplan", - "construct_building_from_ui_state") && + "construct_buildings_from_ui_state") && Lua::SafeCall(out, L, 0, 1))) { return false; } - auto bld = Lua::GetDFObject(L, -1); - lua_pop(L, 1); - - if (!bld) + // register all returned buildings with planner + lua_pushnil(L); + while (lua_next(L, -2) != 0) { - out.printerr("buildingplan: construct_building_from_ui_state() failed\n"); - return false; - } + auto bld = Lua::GetDFObject(L, -1); + if (!bld) + { + out.printerr( + "buildingplan: construct_buildings_from_ui_state() failed\n"); + return false; + } - planner.addPlannedBuilding(bld); + planner.addPlannedBuilding(bld); + lua_pop(L, 1); + } return true; } @@ -373,6 +377,29 @@ static void show_global_settings_dialog() } } +static bool is_automaterial_enabled() +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!(lua_checkstack(L, 1) && + Lua::PushModulePublic(out, L, "plugins.automaterial", "isEnabled") && + Lua::SafeCall(out, L, 0, 1))) + { + return false; + } + + return lua_toboolean(L, -1); +} + +static bool is_automaterial_managed(df::building_type type, int16_t subtype) +{ + return is_automaterial_enabled() + && type == df::building_type::Construction + && subtype < df::construction_type::TrackN; +} + struct buildingplan_query_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -583,7 +610,11 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest if (!is_planmode_enabled(key)) return false; - if (input->count(interface_key::SELECT)) + // if automaterial is enabled, let it handle building allocation and + // registration with planner + if (input->count(interface_key::SELECT) && + !is_automaterial_managed(ui_build_selector->building_type, + ui_build_selector->building_subtype)) { if (ui_build_selector->errors.size() == 0 && construct_planned_building()) { @@ -679,12 +710,11 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest int y = 23; - if (ui_build_selector->building_type == df::building_type::Construction - && ui_build_selector->building_subtype < - df::construction_type::TrackN) + if (is_automaterial_managed(ui_build_selector->building_type, + ui_build_selector->building_subtype)) { - // try not to conflict with the automaterial plugin UI - y = 34; + // avoid conflict with the automaterial plugin UI + y = 36; } if (show_help) @@ -920,6 +950,12 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) // Lua API section +static bool isPlanModeEnabled(df::building_type type, + int16_t subtype, + int32_t custom) { + return planmode_enabled[toBuildingTypeKey(type, subtype, custom)]; +} + static bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) { @@ -950,6 +986,7 @@ static void setSetting(std::string name, bool value) { } DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(isPlanModeEnabled), DFHACK_LUA_FUNCTION(isPlannableBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding), DFHACK_LUA_FUNCTION(doCycle), diff --git a/plugins/lua/automaterial.lua b/plugins/lua/automaterial.lua new file mode 100644 index 000000000..1cd7e9faf --- /dev/null +++ b/plugins/lua/automaterial.lua @@ -0,0 +1,23 @@ +local _ENV = mkmodule('plugins.automaterial') + +local buildingplan = require('plugins.buildingplan') + +-- construct the building and register it with buildingplan for item selection +function build_with_buildingplan_box_select(subtype, x, y, z) + local pos = xyz2pos(x, y, z) + local bld, err = dfhack.buildings.constructBuilding{ + type=df.building_type.Construction, subtype=subtype, pos=pos} + -- it's not a user error if we can't place a building here; just indicate + -- that no building was placed by returning false. + if err then return false end + buildingplan.addPlannedBuilding(bld) + return true +end + +function build_with_buildingplan_ui() + for _,bld in ipairs(buildingplan.construct_buildings_from_ui_state()) do + buildingplan.addPlannedBuilding(bld) + end +end + +return _ENV diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 73fe160fb..04f6debff 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -5,6 +5,7 @@ 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) * void addPlannedBuilding(df::building *bld) * void doCycle() @@ -64,7 +65,9 @@ function get_item_label(btype, subtype, custom, reverse_idx) end -- needs the core suspended -function construct_building_from_ui_state() +-- returns a vector of constructed buildings (usually of size 1, but potentially +-- more for constructions) +function construct_buildings_from_ui_state() local uibs = df.global.ui_build_selector local world = df.global.world local direction = world.selected_direction @@ -76,22 +79,40 @@ function construct_building_from_ui_state() local pos = guidm.getCursorPos() pos.x = pos.x - math.floor(width/2) pos.y = pos.y - math.floor(height/2) - local bld, err = dfhack.buildings.constructBuilding{ - type=uibs.building_type, subtype=uibs.building_subtype, - custom=uibs.custom_type, pos=pos, width=width, height=height, - direction=direction} - if err then error(err) end - -- assign fields for the types that need them. we can't pass them all in to - -- the call to constructBuilding since attempting to assign unrelated - -- fields to building types that don't support them causes errors. - for k,v in pairs(bld) do - if k == 'friction' then bld.friction = uibs.friction end - if k == 'use_dump' then bld.use_dump = uibs.use_dump end - if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end - if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end - if k == 'speed' then bld.speed = uibs.speed end + local min_x, max_x = pos.x, pos.x + local min_y, max_y = pos.y, pos.y + if width == 1 and height == 1 and + (world.building_width > 1 or world.building_height > 1) then + min_x = math.ceil(pos.x - world.building_width/2) + max_x = math.floor(pos.x + world.building_width/2) + min_y = math.ceil(pos.y - world.building_height/2) + max_y = math.floor(pos.y + world.building_height/2) end - return bld + local blds = {} + for y=min_y,max_y do for x=min_x,max_x do + local bld, err = dfhack.buildings.constructBuilding{ + type=uibs.building_type, subtype=uibs.building_subtype, + custom=uibs.custom_type, pos=xyz2pos(x, y, pos.z), + width=width, height=height, direction=direction} + if err then + for _,b in ipairs(blds) do + dfhack.buildings.deconstruct(b) + end + error(err) + end + -- assign fields for the types that need them. we can't pass them all in + -- to the call to constructBuilding since attempting to assign unrelated + -- fields to building types that don't support them causes errors. + for k,v in pairs(bld) do + if k == 'friction' then bld.friction = uibs.friction end + if k == 'use_dump' then bld.use_dump = uibs.use_dump end + if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end + if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end + if k == 'speed' then bld.speed = uibs.speed end + end + table.insert(blds, bld) + end end + return blds end -- From 65114d904c97424c90637a1571c9d224ffc7762c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 29 Oct 2020 11:53:31 -0700 Subject: [PATCH 063/112] fix typo in comment --- plugins/automaterial.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index a238216c8..7940fbdb9 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -1100,7 +1100,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest // Allocation done, reset move_cursor(box_second); - // if we're using buildingplan, we nevef actually leave the placement + // if we're using buildingplan, we never actually leave the placement // screen, so there's no need to re-enter the screen revert_to_last_used_type = saved_revert_setting; if (!use_buildingplan && !revert_to_last_used_type) From 821a7f09d58c54887c724249c2bd61af7174d5c0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 30 Oct 2020 00:58:01 -0400 Subject: [PATCH 064/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 915f88055..861b4349d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 915f880558a2ed5775beaa5cecdbf2825483aef9 +Subproject commit 861b4349dc7bc542465de3c168af3ca18561e0d6 From fbc26336cf763621f051484bb00bb479e297a220 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 31 Oct 2020 02:25:26 -0700 Subject: [PATCH 065/112] don't show quality properties for no-quality items for items that cannot have a quality or be decorated: in place mode, don't show quality adjustment hotkeys (or isDecorated flag hotkey) and don't interpret the associated keycodes if they are pressed. in query mode, don't show quality or decorated fields. --- plugins/buildingplan.cpp | 80 +++++++++++++++++++++++++----------- plugins/lua/buildingplan.lua | 32 +++++++++++---- 2 files changed, 82 insertions(+), 30 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ad0bdf358..8b4c94493 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -313,6 +313,28 @@ static std::string get_item_label(const BuildingTypeKey &key, int item_idx) return s; } +static bool item_can_be_improved(const BuildingTypeKey &key, int item_idx) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "item_can_be_improved")) + return false; + + Lua::Push(L, std::get<0>(key)); + Lua::Push(L, std::get<1>(key)); + Lua::Push(L, std::get<2>(key)); + Lua::Push(L, item_idx); + + if (!Lua::SafeCall(out, L, 4, 1)) + return false; + + return lua_toboolean(L, -1); +} + static bool construct_planned_building() { auto L = Lua::Core::State; @@ -466,13 +488,15 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest OutputString(COLOR_WHITE, x, y, item_label.c_str(), true, left_margin + 1); OutputString(COLOR_WHITE, x, y, get_item_label(toBuildingTypeKey(bld), filter_idx).c_str(), true, left_margin); ++y; - OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin); - OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); - OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin); - - if (filter.getDecoratedOnly()) - OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); + if (item_can_be_improved(toBuildingTypeKey(bld), filter_idx)) + { + OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin); + OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin); + OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin); + if (filter.getDecoratedOnly()) + OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); + } OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); auto filters = filter.getMaterials(); @@ -594,20 +618,27 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest return true; } + + if (input->count(interface_key::CUSTOM_SHIFT_M)) Screen::show(dts::make_unique(*filter), plugin_self); - else if (input->count(interface_key::CUSTOM_SHIFT_Q)) - filter->decMinQuality(); - else if (input->count(interface_key::CUSTOM_SHIFT_W)) - filter->incMinQuality(); - else if (input->count(interface_key::CUSTOM_SHIFT_A)) - filter->decMaxQuality(); - else if (input->count(interface_key::CUSTOM_SHIFT_S)) - filter->incMaxQuality(); - else if (input->count(interface_key::CUSTOM_SHIFT_D)) - filter->toggleDecoratedOnly(); + + if (item_can_be_improved(key, filter_idx)) + { + if (input->count(interface_key::CUSTOM_SHIFT_Q)) + filter->decMinQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_W)) + filter->incMinQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_A)) + filter->decMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_S)) + filter->incMaxQuality(); + else if (input->count(interface_key::CUSTOM_SHIFT_D)) + filter->toggleDecoratedOnly(); + } + // ctrl+Right - else if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter()) + if (input->count(interface_key::A_MOVE_E_DOWN) && hasNextFilter()) { ++filter; --filter_idx; @@ -706,13 +737,16 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest OutputString(COLOR_WHITE, x, y, title.c_str(), true, left_margin + 1); OutputString(COLOR_WHITE, x, y, get_item_label(key, filter_idx).c_str(), true, left_margin); - OutputHotkeyString(x, y, "Min Quality: ", "QW"); - OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); + if (item_can_be_improved(key, filter_idx)) + { + OutputHotkeyString(x, y, "Min Quality: ", "QW"); + OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); - OutputHotkeyString(x, y, "Max Quality: ", "AS"); - OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); + OutputHotkeyString(x, y, "Max Quality: ", "AS"); + OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); - OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin); + OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin); + } OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); auto filter_descriptions = filter->getMaterials(); diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 73fe160fb..51a918afc 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -31,17 +31,20 @@ local function to_title_case(str) return str end +local function get_filter(btype, subtype, custom, reverse_idx) + local filters = dfhack.buildings.getFiltersByType( + {}, btype, subtype, custom) + if not filters or reverse_idx < 0 or reverse_idx >= #filters then + error(string.format('invalid index: %d', reverse_idx)) + end + return filters[#filters-reverse_idx] +end + -- returns a reasonable label for the item based on the qualities of the filter -- does not need the core suspended -- reverse_idx is 0-based and is expected to be counted from the *last* filter function get_item_label(btype, subtype, custom, reverse_idx) - local filters = dfhack.buildings.getFiltersByType( - {}, btype, subtype, custom) - if not filters then return 'No item' end - if reverse_idx < 0 or reverse_idx >= #filters then - return 'Invalid index' - end - local filter = filters[#filters-reverse_idx] + local filter = get_filter(btype, subtype, custom, reverse_idx) if filter.has_tool_use then return to_title_case(df.tool_uses[filter.has_tool_use]) end @@ -63,6 +66,21 @@ function get_item_label(btype, subtype, custom, reverse_idx) return "Unknown"; end +-- returns whether the items matched by the specified filter can have a quality +-- rating. This also conveniently indicates whether an item can be decorated. +-- does not need the core suspended +-- reverse_idx is 0-based and is expected to be counted from the *last* filter +function item_can_be_improved(btype, subtype, custom, reverse_idx) + local filter = get_filter(btype, subtype, custom, reverse_idx) + if filter.flags2 and filter.flags2.building_material then + return false; + end + return filter.item_type ~= df.item_type.WOOD and + filter.item_type ~= df.item_type.BLOCKS and + filter.item_type ~= df.item_type.BAR and + filter.item_type ~= df.item_type.BOULDER +end + -- needs the core suspended function construct_building_from_ui_state() local uibs = df.global.ui_build_selector From 5ac1df96db6f0733cc4eb811469c9ca99948318c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 31 Oct 2020 02:30:26 -0700 Subject: [PATCH 066/112] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 21676d333..f8823c83d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -44,6 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `buildingplan`: min quality adjustment hotkeys changed from 'qw' to 'QW' to avoid conflict with existing hotkeys for setting roller speed. max quality adjustment hotkeys changed from 'QW' to 'AS' to make room for the min quality hotkey changes. - `buildingplan`: new global settings page accessible via ``G`` hotkey when on any building build screen; ``Quickfort Mode`` toggle for legacy Python Quickfort has been moved to this page - `buildingplan`: new global settings for whether generic building materials should match blocks, boulders, logs, and/or bars. defaults are everything but bars. +- `buildingplan`: desired item quality and decoration filters are no longer shown for items that inherently don't have a quality rating and can't be decorated (like blocks). - `quickfort`: The Dreamfort sample blueprints now have complete walkthroughs for each fort level and importable orders that automate basic fort stock management ## API From 8d1ba5a83a5e69639fd93e8f595372dac4a45aa5 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 31 Oct 2020 02:34:06 -0700 Subject: [PATCH 067/112] remove changelog line -- this is new functionality --- docs/changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index f8823c83d..21676d333 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -44,7 +44,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `buildingplan`: min quality adjustment hotkeys changed from 'qw' to 'QW' to avoid conflict with existing hotkeys for setting roller speed. max quality adjustment hotkeys changed from 'QW' to 'AS' to make room for the min quality hotkey changes. - `buildingplan`: new global settings page accessible via ``G`` hotkey when on any building build screen; ``Quickfort Mode`` toggle for legacy Python Quickfort has been moved to this page - `buildingplan`: new global settings for whether generic building materials should match blocks, boulders, logs, and/or bars. defaults are everything but bars. -- `buildingplan`: desired item quality and decoration filters are no longer shown for items that inherently don't have a quality rating and can't be decorated (like blocks). - `quickfort`: The Dreamfort sample blueprints now have complete walkthroughs for each fort level and importable orders that automate basic fort stock management ## API From 2c65113bdb2f77b2644e34a50e3509c195514903 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 31 Oct 2020 03:03:05 -0700 Subject: [PATCH 068/112] detect all conditions that make the game pause --- plugins/buildingplan.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ad0bdf358..f5671fe0f 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -897,12 +897,19 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan return CR_OK; } +static bool is_paused() +{ + return World::ReadPauseState() || + ui->main.mode > df::ui_sidebar_mode::Squads || + !strict_virtual_cast(Gui::getCurViewscreen(true)); +} + static bool cycle_requested = false; #define DAY_TICKS 1200 DFhackCExport command_result plugin_onupdate(color_ostream &) { - if (Maps::IsValid() && !World::ReadPauseState() + if (Maps::IsValid() && !is_paused() && (cycle_requested || world->frame_counter % (DAY_TICKS/2) == 0)) { planner.doCycle(); From f053ccf14bfb51cf838fef515ee3ec54194ff8fe Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 31 Oct 2020 03:05:55 -0700 Subject: [PATCH 069/112] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 21676d333..bad645f87 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -35,6 +35,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `buildingplan`: artifacts are now successfully matched when max quality is set to ``artifacts`` +- `buildingplan`: no longer erroneously matches items to buildings while the game is paused - `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences ## Misc Improvements From b8e20ed73830656efd7a10b58ec7811a752cf72d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sat, 31 Oct 2020 21:23:45 -0700 Subject: [PATCH 070/112] remove pngs and add comments to online images --- .../bedrooms/28-3-Modified_Windmill_Villas.csv | 1 + .../bedrooms/28-3-Modified_Windmill_Villas.png | Bin 5757 -> 0 bytes .../bedrooms/48-4-Raynard_Whirlpool_Housing.csv | 1 + .../bedrooms/48-4-Raynard_Whirlpool_Housing.png | Bin 546 -> 0 bytes .../bedrooms/95-9-Hactar1_3_Branch_Tree.csv | 1 + .../bedrooms/95-9-Hactar1_3_Branch_Tree.png | Bin 4312 -> 0 bytes .../library/tombs/The_Saracen_Crypts.csv | 1 + .../library/tombs/The_Saracen_Crypts.png | Bin 11666 -> 0 bytes 8 files changed, 4 insertions(+) delete mode 100644 data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.png delete mode 100644 data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.png delete mode 100644 data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.png delete mode 100644 data/blueprints/library/tombs/The_Saracen_Crypts.png diff --git a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv index 006c01f55..4524261d2 100644 --- a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv +++ b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv @@ -1,3 +1,4 @@ +# see an image of this blueprint at https://i.imgur.com/XD7D4ux.png "#dig start(11; 11) 28 bedrooms, 3 tiles each (efficient layout)" , , , , , ,d, , , , , ,d, , , , , , , , ,# , , , , , ,d, , , , , ,d, , , , , , , , ,# diff --git a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.png b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.png deleted file mode 100644 index dda3673e26f2926131ebd84013f2eac6e293a3e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5757 zcmZWr1z1#Fw?0Tpi!|Z~(l~T?w}5oVfG{*D4N}q}-3VB8*C5>?$c%*45RxN|gc3t@ z=kvSY{qKG5bIv~d+0R~k@3r3dJ!`M{r#fmRg!F^}0FY>?E9+zWY)l&^z{7kAqiG#5 z-CZwrGj9MOy8m~>0`lL`Vy1MRm6V=7b@KG_^mg*}V$x7jV)A<7>FDf+SrM>UXaF%Z z*rAp|uN*6C#f4{TdFoRUFzG8MhEt}ob2Hy1*NkI+w?L)WcTYtHhhg+>9CmVYcoLN! zh&X|89)FYheRAZ7xP-CGwcrn~vz_SeiJO)=S!DH5VG|s`mw-51T}V$PoVY^q;l1yX zBZI?ho4oQ7gsffwIYHAiX76hjEZ{6uTKe&$Ui@wVD`1+40O-{&=;eP9drP*fm}iL< z9);EGlP0c35Kalm2PG?(0`jU@;f1;E2Ebb!z;@i;ZUcDA1K4teZBGN?g=cxeSb#+) zGZog`G=S;8L!2_;Bn4E%jT2M=Q+|NbQEN~FnCAibHB21Uf%;aU7e-Fh2oMkh{CaWG zTmVimU^~jf5&%Ty0hFr0O{9N4t|douW01;glx|~{P>HZ0;CYU3V#5276Q)7)m{Jsc z2b?d@+aH)mCm112_WkG!0K843!bE#?9RMS(g~3FVp`I=Znof6Uh)92 z<`X)31LA9<50}CVcfWbhwvXdvP4w0mo#@y|Cf@?Q-B~m_^70_}UZv4E%1Bwmept~9N;|-6h ziK~X`>y4|iJK|65;SK&c6cH-DcSUjh6`8;f71T|inCi@{moq(J6~4E4SL`l!lwLo_ zql|K`X7=`b=YzC%f&#Io>L8X`ij@0&J}*9VZN!(z1Tj4xjovKW9M}}vWY}c>Z9!OQC;$B8nZY`z{+Np<-4^+l z@D`p!uAqX^hkX6z@^^;!L{iKZYu-Wi>-1vF?+g3pOH4#kYiO0!yw94ZccO7ZJJ(j@pl$AK1tK#3V6Pkt1;sPPzhGd|C*dKW$y^lTy0rM7j|}elY=5 zE;5fS&(p}18_NBXDs7A~OEZf-i-DWZu%fcGa;1{U=*Vc?Fr~uUAf#5+NXTHL;_gg# zl~HBkQ2?rA9ZG1Tlj9?xvx$$+lXN4>WVHXEJ8Ohoo{O#vWgSCXn2 z-w2UG_J)atmwwzGxz~&xi87TH?^p7$f4IvCg5P{TA7`bZCa&XrB|hf>JqKpD=RGRQt2?ej38AQ(d4k?P=lnva0`(J z3kJ(&gk{v)GLk2?yp^sc4DULW{d+vh_H&E6t;36+c$TdMb|Ehwgz#M(Ub|nEov0C; z;x!So;vtA(w9-Q&&po9X`9=`G^pvayT+tsI<08=?~vjU#pTOIS*TIux_{y zyAOY0$75$^*V5kA-p|+9ZY$*~m3kueWMtyY#QTZaJdpw!fjL2B0kUAOV71lOJir_T zEr)(`&}iH=FE!hTc6vmcOPH0I8kt))sn!(Mu+4VVbd=pIYtzZlsVbU<4AlREeuh@G z($#Ox;~{FH?$8i0#Eu5iI#fHbpV5^O(jM~qS_Ypac7e2t(VQ{VyZHsBq_N~mKJ3lD zT*%OI_6{G!6iyDG7utZ{ALkl$E;J~ZR)f$$+Pou=;u7PvUXAnS$nq923@?v%ZEE_Q zJhxg4nmQ>gXe#L6D@WAri|#w`$1W9r5iaH`G86LtIxwpAU2khrj;J_`-*vS*R98ti z0@8wH;j`yUGpU)YZ!Ph!M1v1#WKU&Z6mQ6GNQ4UR4TX zxqWsUbUO)@g|lHnu;U{XA}sD)o&~*R?n#!DL^zx_F6=2Jy=_yVQb>7K^J=TFhS_QK zXKD>I8kq}AgE^B%KMnmc`xPI&4Ne>gCFY2ZjW&sqOZJL0rJoeY5%Hf~Qu1C^zEI9q zUMYYG=t|V_Xo_&lR`R+?FN(Dbx62LlI+}g?W?E>f;imOKf|t8i)WfZN^=j`ZcD-<& zXd{>=mv7X{EASnx|2Vpgx`LMiB$+M$Bu+;>^H71DyPP_S?e~3uwq(M_WC3{l=>uq8o39JQ7>^9%kWxc5O(1gesQ)Ff`|!$q zS1tW10m|+EO}A>#B&RmCi#{VY2mMeBOUpx`=;=~pNB-0#OU>#;dK@-<-pAy^xakTLlVhFzog*mHT>zn z{1_pOd@hRlzw=cK4Dv56>@3=KzsOF`?ydw~sUiL%dm}7u7N~h-_}O`J=hbuuml=pne7V#b$jI@WXx{!Guw3GX+O{v@_BBeT|V3xl>`TTA~Y~45cWRhPeY2&`@ zvnzd*F|9k&@unlcw$0MDZ9k~1CD;+Y!%!p!TR!dz{6&2``>jYqX1VJq$P+z;K3Sz+ z4t>FK4IA%R@aNdC>D2ACIhNm3SWsGy{U&E|vowFO6z0XW+`PXTuce{p3qAXWnuAT|PQJ_eos-ey z{<7qHw--fMhZyc3e%SpvKuEy)*!Xg<&wt6E@!-QMneOIGrZCQ1r}KsrgRfa3jM_2YjJP1s^i33s4Rsev8Iv8JIDXy}G_O00glCKzJkooZn*l9RTp<2Y_8$ z0FcZD02=-~$U_ z$rG^SnEC+ZccOrT);Z92W^(UW_mfD`{hJubiXh@m^=)Brhk zcs<6T*Q2W+GBkQhUEs|FuNtHVH~hN_voIjq#vWx_fI3GTKQsk}za#(muT;F=9>dx$l6#A@`=9a=)HDBM4R z9gcVQvl%D0v<*WaboI21wJmzbC2v_;-uK;@|I4@l>pzx2-J$DBREN~@_9jp5$t=fhr8Bm=AI`0$qPI(V#o{GzM45EUq9rEF&Eh^ z6E0@gKWJl2yGI^dPS6DzyP-5(U$keBw1k|V$|_eY5dI1HAAqgbIAC8B2cb~d)zXE8 z+CP&RKK=mmGJXh6C5yU_wCG8JpI^tox#7bCeJ%3ULWF}t&; z#=g!plJY;ZKxcne7lT?Iq=j}tF$w-{9D&F${>S29a!j*^i1kg3Eh1+o8D5007TXVR zr_;2ZpPO*Qn*<~Nw@4yr){wOd?S@4VnL6}Gv{1ht7I8H2M+O*`t!_|xFlLyeu4)^g zH47Gjw*M`!Ke`Nvt{a+N$vC>i@&AAN3W#>?vI;@1hGZS}{+}J|wy0`xXDKnH{zt^& zLoTkVcYwryOy*9k&Uf3{OJ*t^&WJD3KmEAI(;Gn@moE{} zOxDjo+b+7xI1^M?znL4*#@v=$SDAxW(Hfe8a|EubrCf|DBa;#`@DCfm)QgaMbG}tq zFn;10VUqLiFcW;PEvKH$^vcTjm6gUk1CUQQ)2_CW29P$k@>v7*fcpZZ3p0 z+Q>+c9B12;8ApSw!a09(s2r8wJo~Yw6tO2t;b@kFwlXWPmb5(E>(#L?CZMamlr;~#GvK?m7*X7A6nF<78kW^S5NXx#Z zWp?=eC-Zb^_-FLBUT|amZ|O2HEp^TE5`kS#id23`wL3zWB%!JP=Y2c{dF|=1{KSrb z5$$-1f8FtURFWw$t&yHe{wjHdrBJ7AAx3LDb6vc&7PTK#L;lL6m0>GTrwBQD{<4%+ zgOlKn_vExN8~jMsu6Rm#VpOsr{O848W=z2v6WCp zgVkdPB6xEW>597Tq7&cK-c5`)rW_g=@Jwg*X|o(X>eM^`nEAmT6CCld9JR3?r9aIe zuei;3)QL;X1JUX30}Mf#NC{snNOD^*Nlt0SJxMVq+8ANtND1nZU$KmX4=(~nUGs;p z@D7@!CYrIm3J#HfabeFDkM~kX-`?!LLj-nh=ItAM7&cyLs7Nad@0d^idI8Di~;|;8sNu(##+K!lDUIp zWJLF_p+0-DPd3M$P4@ACVfht0J={t)15Ep5B-igkEmdDX+rjZuD+!@R1=O8uym8Xf zCksL1))H`b<}|wSs?-@1^`xI{agPsk>78}36_@Xvvvq_te4?DW#E1S~fXA2NwHIwk z$;CPxlErXCBPtAt?ubw)tJ{k)+_eV(#ls~jceox#0E0^cb8Uk&-USbvYDUzMNDT=x zIs-B-g0&@Q%klc?`b>RFm9*!o52FzgT_bG=#zrc0s4UXhf$FBilqd)EZbb!8!mIXL zq0)3DqjX9=AN^n6?&1iARDF8gbQzY@ui5*k`PYL=<;x2BrOeEOIn`ONbh9xWzreJT zWRU~>spS1n#N5czWxV}BXR9faD>?g}-k!Nq0%Tf0(`i(|>i?6K|KuYASBog}4>Re`3B!ASm&e20t4Q<5Cd5|&_!py1 zGWK?$j)PhL*HIQRMu{y)c!O+rfz0w)qwt6|W7ift>fC+j$lR3?r_ae|6)l>A@aG#b6&zTX6< zb-4~Z5oZj}a`&gM;v~#y?^9Y>^Li_+z4KDGpk5=tG}9sPGmB)QPU0`MRrF3!S2{9~ zeIlmke#}~I-;9h`$FgISlH$jeK7A?p5MWV>k^cD!7n-El%VEnG6a zBdnQL7(5ki5YYq*7FUx_w$E_ri}(#Wb`m1X`_fSAwGuSv%Pu8CaEI`fZD!3_9|UnB z+cfY~)AWiwqR=x7DM6+3oP!?oFT^{KZ2Vfg`XHV>@l;(ImIA5yEbRQ;gSHjbavXMZ z+4Kcw!&O~bS&nK?qbbi6By*pCJz-IVWH;JMQ=Mo`+HHSWaxZMo-pU5don3MZk3Uqy z)FTDtcAM7?_@g5ktOA#fAfaNeas3>J3eQ$Vp@~zlsbZX&?(YuNdAUihL!Jl17(>Rv zWqYkFhNlXaR+2DCElhV&-TuQeVPf3zo*#oR(>{SF*D|zb)*RJ+;7c3R(PkB)EO!{( zsff8@{`-h?}(4Q=gPb3i8g8S2u&Kf)SIJ`_F-43 zZ}|BWO|D8*kT6epJt&~i0H&C$dS>>Nck)K&BkXWY^fdh`sGe2hi=B%m*Ty*}^^RTL z>oh%F^WsNFViu5QVX`od7)9A5OvYEyOWLN8#?Lz{Kh2aJFm`QUe{JPwZ4c5m$Vk67 zD>w86 GtN#L58|9b) diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv index 8906ae4ba..a690b4cf3 100644 --- a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv +++ b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv @@ -1,3 +1,4 @@ +# see an image of this blueprint at: https://i.imgur.com/3pNc0HM.png "#dig start(16; 16; central 3x3 stairwell) 48 rooms, 4 tiles each (more aesthetic)" , , , , , , , , , , , , ,d, , , ,d, , , , , , , , , , , , , ,# , , , ,d, , , ,d, , , ,d,d,d,d,d,d,d, , , ,d, , , ,d, , , , ,# diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.png b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.png deleted file mode 100644 index 24eb4a22af2addc52ef792038d1145c2a0af2841..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 546 zcmV+-0^R+IP)500001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA0k=s+K~z{r&6Yt< z12GIm2X27acFBP_DhFcOE#e5^ANni%$H5*!VV-1tJ9eDZ?F`&)+xB602<|U>2!3># z_w&oe`$FTrEH6_X#Q5#>G<+!N?#oCo(>dtj^gQ(O{2G+&pwmae4|tdrO}?u7Rjm87 z9PK(8O9b_w5HQxdO1phPc(gG_j@uUzHd@tHd6LzEaG7l5t?`pP=47&_0P7kPtXDCq z`jGKUhFQVntEykcxar$B&w91pJF3XeYGT8yQLMMCN$>^pkCRo>) z?CAr>FPUbA?U=r*`kfdyy=Qn+D;`Q1YaZkFx~e0Z_oOeq>L9>o{NAblkWL@vIwmWc zd{y3R(MLDtEbW~h*;kRFqLZHmVrOXbNL!eBH;M(LFq z(-YAuYYZ(jQMLx5rxA^%_x5()^Esb$KIfc2?$7VOuHSuMzia(o*L`PUk2p(7?2-Tg zK+4s{5eEPwD&Vyj1_3!0^JTlhMI;*Mj0PBeDtwTDhS+250pQ+s$@M@{kXAeGg2Mnn zk`4f*kO4plvQj<+K&%k}%$@=Os}cZEj3{sOvH<{ux2vN)KH=n-GW#3c7t#(Sfu%5D zc>PM8fF^3{qHpr!q1h9yJtFmPXMg_a+5mb1+6_Blcbh|{oN+$-sR*tKX~d-Efmd244Ms| z7F1P`22eV;A@p=mwCq%zG=wZ6gQ_Y4C`d6kOVCX=Kxz<#OjpvIQd=ra`Fm`_KZrdX zGlP~9$?7l^`TY4Pgbafirb^#mzzv;ESB1!?`a=c=Z$n{&iBRuL^;PTV1r3WAUiuMx z9WPhZlzjFKvYYy;rvKO_DMG4IFfrmGIJSg>WKrOpOfZ#=lwP`!XB|8f|6rN#aH^cY zc3x3?U z6eJ{@1?Th^_}LNIHGS1kD>UaT{y0mAa$=Z!p~Z=ou(ndPa?Q3FyMunZcVpxfIUD=e z)L-F!u@2Q2eOxozg%Lf=94yvI7TyE%RTAUv4uR{<(u=b zrZP5*p*OT|o$*#2^S!UT78T<7(;d=%V_@ECyx4oZ83{>#n@&`K5I$7(?T72Rqwg!G z2AoKJMTgCwRcs@`@QGwu473sEp;32Nt5TA_{zTmNVjLF zO}4gpfxD=6)vgtQ+XfQzdgkNzQp<{>#qKF=(s?a4)-(Ua+!Ske`#mUrd%;GB@r3nY(Q6Q(-*G$YP>uK?!juB4R{0z^Uq zh)eS3nHV(`P3QNU%6qg?G}WiQfow!Xdq19U{b6U^@SV2E`A_1)y=qtm*%c{YXTT~< zmJZ3s7}(Puwa58fswtb6n>GWp8-4bTXgzwo5vxDbw##l)f4B3{5tK2p^*CrCCcg5^ ztML>Q*qu|}RBJP zrn9;}s0>%2Bn?LlNzbirRt?9bk(PP&VF6$)L%cv3{4E#2Gst?a$i&(VhK|Nze}mjz%qnscYd$o80E8poJ$M zFInU;_lDkcR>tNO;efkEPvpBQ*Fr4eoa{G!+n7nu zv;SFn>XBAHd(OZFS=zPlX+k!fZpy1$WQ;u)5m$9V8Fv>Rmy^DJXZRNt=Fh z)zqFovM>OeHmfDBYT+J4L@!f{BP<4RxsG5Q6Cx_f#y{>^2 z4(kENly#a(iW_Yg?s&-dj5ieQM?_$jif(f4{0oR1trpB^%H`A!!{Dg37_YWvc%gej z-X%p77OBl3W=;-Syo45>dE33X&!_#KpE~ovQJgbhPP#pr(wtW~dQQ}2qtC;zb+Qf{ zyKR=qx-&7EZWY=4an{pJPG#qTuVIbM>4;^}>Sxw=pOBs=6?hqHzf@NBm{YXED)Jj< zd?^pJVs-fV!}E63X^CC&hCc0`xo$EGe#w^W=|t-)(BA`E!6zs`>8i5Q`;WzKXcj(Y z4iuIegmPJa8xkf+TV{0Q;%u??MOmS1z<@vT_M^LrZy%jkT`cY5REFtT+~Ln?w?%;p z!4;Z@i;aZ&7^ZsUGKg}v{F$jf+@o(y3|sx-?Bm8EUP%0wFU;+WyDV%eLd&)+b#57i z;ug9;W*-@n% zJgeUVBa$r$W46W3>;+YUyu1OtaP#ZDt2fW{0ntH@zPlB_ucNVM?=!Q?_g(eE1y%ts zb_Kh>KKLq@ODZg7)P;!=c7l%4e_Df8s9~ zgIkJ@PWvg_`O;gPL>V6;-RfUy+xwW;RPC8GBIi{#uXMPcA0}I6Q*S4znEaPORlO_O zC##c7&iwhZr90p4w!lA;N zSw|l6k87439q2v|_P)htq5EQnBP&NsDz>ESi*w)s%*25^uj5zk>+9!NqMl6#Q7sjT z&b_tb>HW~FukN~yeH5qGH6KNv{r$Lz73TD~K_OBkLIxj(T+_yjC5nDFy_(vDr$akz zv7i8RiNYvg7t8~k4(r0p6W@c4J?@E$(N%y_`XF*TTo;iZmcB=Yx4$Oq5oz~s)x0GS z2H_-fZcRHzy<6?=rMmjzZ(pl$&x+-5{uufNotVOPe6)m;cr zls=3P_WQWj`2KdaPh5OUO@lD&1Mfnu51`$p7cn-gF5*lpXe60-OC6~%S~YsxLJD6o zBF~0&f+2#okwV2aSyjnOAb2L=HZsMz&l$3KpNa2xJ2eWG8>XY$Gh-?ndKSWNROE|a zCp8`@vChQ}ahSFUdVG67bB!R#5}!Y%r5N)oUj`;MLpiL4{90cy9oYd+m4fm!PaZuu ztppAUt|_wg#r@mp32n(90@waHqZ^V1>iL1P4!Wa%f4y+)`elQ8jh9zQ`zXSjD2^0$ zZKB+_J=t(7;|(wEw+y-@2wk&F0Huv`BGx%_PA}*97wIp3t3{92{LX^qfVWL3YKRs! zKWECiuags#9Je;lXj1ZLxw1&evHBaO6AW#<@VWb_3NdsGbi8bTm01^9W(pJ}jPUop zCmSA32n|{)wJ8hcCl7k#}yd8R($Ms=EACKkw^|?Dfb|kv^fkpdB%X~YT z-tpP9D8k_rSn`yl*ZuD0QyGqfi`6WNi=5}RaFrsXuVEOyocP|`!+!P?eN(eOC0_i; zx6Yf%>>u?Mj{#w*^ofUoAb{i08EEOyG?3S&1_X+KV~s3L3O}t5;LZS4@zY<=;F!rk$d>HiHxP$ zedinTwUlp_wg>}Z0L_`s9$}Wrye<{tFrLDYwUuHnsIzk-K5{B zfF~q(WfW1Stii#x!PMt3y$i1kRNLkZZ1qanx0Y7Qo9H_kN6M=vDSK?&c_dos>+9BCh0!w_fTDyf!3;DJ`)!J{rY1 zFC>yW1M@i3xxX01co(h^o@sexx9PcGS~B?TWq78&_vztD_jdQD_k!-lQ?C6BEuYnw z6jO<5Cpk??&4kALX%2)7!*6p@nKu(~;lYr1jo>h5XJc(KOw0~*m!k0r+p!*{+>o_>4dkt+!kMO!=U)5xp z!DC9AYQUj==uph^9;g*$z$+gcI2I0zfmVU+ch7L9LT`YZrxTGS{zrnpQ(#onfSDL`0MT2;hhlPkcVFW}po zC2Nq-D0eUWkGa7J5sg3Pl+Gv~Ccq|(s{iOz?p1?0YUmohFwCVRUhH!cO*bjEhhA|! zaw^YXiJ+e6n=K&`Vwwg+WR-WQv5n|xwN`lr`MyxG9cr?csrGS!`2YS_AXH=NP>kJO W$H%k8VDQ%i;Ocb5k%2z-$3FoF3IYlM diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.csv b/data/blueprints/library/tombs/The_Saracen_Crypts.csv index ae6107894..5d66a88c3 100644 --- a/data/blueprints/library/tombs/The_Saracen_Crypts.csv +++ b/data/blueprints/library/tombs/The_Saracen_Crypts.csv @@ -1,3 +1,4 @@ +# see an image of this blueprint at https://i.imgur.com/Kcjvx6R.png #dig start(24;24) room for 513 corpses d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.png b/data/blueprints/library/tombs/The_Saracen_Crypts.png deleted file mode 100644 index 6b8d6e03e5767a73f225323574b1a87ffe06eae1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11666 zcmeHN3s@6ZzE6$8-3=OUX#>_rbZxa;(WFp|C=x27)vLgEw+dpJpaSlt8kJViLPm0} zNF$F*Eum6^bxUt6H3$l|V1^hTBGHIeDTt1MJR~57@W^Xs?wLF=DBHUG)vDb+-v?iE za^{>f|M&m*|0f3pEF1ra7ydw@P{uERbIDs2${02H!+tjg%uIO>>84PoQI;=xAg53g+*~QSX*4RO|2U7b+lO4#@dZr_6If+>9eXhZu_iRCzLMdQ3G}59 zlCP4TlmsSV=MBvLGC_zWA(|^O7!D20aZSJqeT-KHJ_7@u>QIis5RowWEf(LU=3g!0 z^$*kxl7Xq5XLoa~));spvj)NAe(GfGZKh*EPF*744AnR$vs_T)ic?ckqHs;Z5W~G4~2_zNG1Cf$EG>;*Y@#cZaNamRb zT2FG^JdC86{6d=|o8%Pr!~{h{i7s&ulj*ppQo1tlS#J8pGs46I-NBl_wv2mrZ7|1) ziMVI2F!?T~(;d#VteN?jWY(LT!sWifrgIx#(NKRs$IbP3A}DL+OM+>KKcrE^HM29m zu3)@)qJTHeO%UOJ@R4l(-6nZH=Fnv!nwnAh>rw4rVg+(e_{>!g!|%@&3gWbvt&)Aj zEZMr0A|7v%AN#lPtraLhzjJso-Y5y`Q7wZ|bM2kyq^Ta;r0+R0TDd=l{OuCFFdi6NR>!z(Hf zUf#3At}F7HS=zP%%or#^%49HMz_AiqjP5v1;djqpF}Hd{oZ~M|=k1M8S6M`|bw3Wi79EjH+gQnPd~V{dQ*N#=J0k}s zKFj3c2N^F5Tf=g>>WIiYNcUV{em8 z`Od-=JcSIrf2G*Zj?xcr)!{nf!~w-2$=-a;yz;lM`lycZ=Nn(Yrr-0o5YFAP^u2S= z@9+s<%XIXX3&a1r=<`7Fr!;C{0q@64?{+xnMaavY-CT=bdm@&sEmOR(x~}S{my&xpSqmrCwE`-lk?x?kzQfOyxDt=&MKT?`Lrg1jOxo$V?@ZW-<3V>A zbQ8Q3i0hr;X&GFWIEzO8fQcNKMa1vprq@UqN~JJykqD~EfPE@hXK$&-MMlcYb1J2` zYPhTx#>T!5st3pjRV{1!Zb=xLaqfC;Vpa&JAWAcPXP+ubN6j+n%axDk8f`9ScLh?i zs2I-%STk<>m-eWWs{PKLJLe&yUqTE@19xfbxl@)_eZ%GAzQ=yB!?Cc2rt8<=ac57M zAli6E!h)x*`5!yX3x79&Srtq4m3R;x3jac#Dvg^y+a4l=UuVE6w@QhS=TVH%whReY zMKp}E%T(;090d`bNgTT<9OVkZoZ6A6n&w_Aw1>&Kli&lfA@(quBn4S@I)B6qS0S59 zM$V8VsP2`V)~h8s{jEc)fAEeIkfnX#*PQoE(XXg6la6}oEqgRCDkj*~#Zdg_Ripkv zBKLi~Ew&%sHOfmhtr~<(iR-?$z=QzbDadEtFunF7PAmGR&NKe?+m+i5wPsD>tRQ8dqCY9Vion`>&NRH=4Rb|OKsHSu2aP4UDR zQ|_kItWVD^<;hBKmX4jw|AfG$#C$F6vyKxu*vhC7CTOh-M=<4t0&+)Yy)*Bv~i6MDTX>Yb_-c_5;Glo((!I%=~}{{@%R zxK4D3;&A!cV9sO!H8GV|Xv(^K3%z|IBHAb~H@Jx@^t~H2vtt@)+RwQ)(em<{c2hAq z$g0iq^0#bLS_7>WIsugJ1KSmLVnV&f7?l~{QOQE!9l9~3H9S2b%N zje4!Xjxgbiyl?C3d0`l)3S5BC6W1CwnS^D!;vFrVJO7-cHk?t5i$-l?q{ zo2Fo~`uK@G_wDV^RC*JY=JST(uAvp|PcIWKj9V*4Nap$LIy5hApre{~_TUI1K2)lK z>$Z%hWp}EOqvfM1r?+X|f!-X$h18??gL0|u?CqP3L#3wJ51FP1WwS~cvU-FZx5j?_ z>6H<>ugfUAU-LG1MT}H%+`sg`nb_D^u7K!1&|9W^yh>#Q8A3u$iE%y!lm4B|{{vpA z&x&^=B}nmL2SBUg=_IBNU&b37C22Al{-GPlXs}hr=xEpnL>n%JB#!ObBmEZBgOCC` zOD3V{iKp%1&M(a2`1mLkAmkTx&F2ICR%7e$!Qjn~bO#+|#TBwOoh$VUmSjp1@8;?X z3&Os&>d3V=fG-TOd~U!x-ED1VESJ#6Rp%Qj?*(sXCIZ9)EE*^AfxY&N6{)8H1j1QA z@}&J4`aXbVe5HcIPy|jO@M}UZ(~rVL%!oy;i61d zp!vnTn=^-BPEU*JOX7hL<&?FGaACfFh&OevD>0MZls-I1U}FQ?MKtO{7uz|qq8WD) z+wG334Fku6#yuilpN^q*2oloFE3lK*j0U^uiC(~#kbZJ{M z6^QVC00sf_+`vq#)+rv>>Onb2XVQ;}gyQ^DukbqWCsdgG>NUUT&_@;Ca|GnCNUy!@S7r zXsw!%hEEqe14^DCH9=Km(Fd-ncjrUsa5@*OqXrqRAMXm}*ro8->Bf z=mtx4&b5(P3NHAP^|TH|QeG2(oIC|iBg;vEa^Sip-mKKsQwLn;qf6~RZ zsqk=Sof%UxRRNvP`E7jLc97;_NX|7^)VO|d&xeh8d|sC=^M^t>{C)&)ys`H_3LX(CB)9M4Ut9z-;y%{*@548hr&)L#NGuer`YG!Ee5L@ zGa`$nM4qj1hp!nOh;#;(Rx<=)fTpN20ZciWctNSzw{u|dbDG_6iwwqP47gh*S6;Lw zu*j}{hCDkCZ~2nu;b%)=iLN*~P~$$Jw?MM)BEcLFpcmLt17b2nya3#3KP{sN+^L={|0ZD9`vdVs;v{+%QnN4+oPrG2^S(iVyeD8A zY+_b8Ou^Y`!}0&KVbgGg{JDY0k}kh`*zGXr5m-g`0{lV+$>G%5%q+ickGY6z6M}(% zP#LxSOS{@pDA6Y1(6|!Q=jPhhSCbqzkgjM_niXW#FQF zq0Qk+e=_C^BKsELLpXykc)^|YsYHsoSpoI~76A6Nm;quhuzC0vi{Zn^gU$wEM9dI3 zHi@*mrME47@caSrEB3O5X?)UZ^yXPi2O0zn4Z6x=(~!f!_alcau;8OP=s!5?BWN=L ze3f<)eQG}NJfXd0{3Vq_jbPSN?J)Pbz?-dp3zY%RL&j*%6$l~AA_7aKM}orktutg< z5%d#zW+#ZAfM%3`PRagLiF*P|c#o?R$9;#$gkWP^J$j+Sq7NXRx^0UUWr7H+Uetr8 zK8b{lhS&;KDYj4xLZqNBml$ob6xri8kVQErH@zVDZ7wm#b#;_H+m>Gm-CVy9;dEF_ z+6B4GxVMPA)lt=MuI&+atEOI`3lharkZt0MYO-E#)Jv)tM!vj7Ua)}PUS`eTnD31m z>W;PwMFP_-RtEm@92uK=6zmSV86@9hc6)#v$YHLaa_Hn6d%5TsoZJJ`rS5N zQNi@RSM14c8m>8_%i#X1&wAALY+L>zBE6%z2NW1Rd_Cz!&CQ#@RwBWq6EwGZf`FY( zc7IwLLP5Cmq&;S Date: Sat, 31 Oct 2020 21:24:28 -0700 Subject: [PATCH 071/112] remove image links the blueprints themselves are spaced so they are readable. they are the text image of themselves --- .../library/bedrooms/28-3-Modified_Windmill_Villas.csv | 1 - .../library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv | 1 - data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv | 1 - data/blueprints/library/tombs/The_Saracen_Crypts.csv | 1 - 4 files changed, 4 deletions(-) diff --git a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv index 4524261d2..006c01f55 100644 --- a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv +++ b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv @@ -1,4 +1,3 @@ -# see an image of this blueprint at https://i.imgur.com/XD7D4ux.png "#dig start(11; 11) 28 bedrooms, 3 tiles each (efficient layout)" , , , , , ,d, , , , , ,d, , , , , , , , ,# , , , , , ,d, , , , , ,d, , , , , , , , ,# diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv index a690b4cf3..8906ae4ba 100644 --- a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv +++ b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv @@ -1,4 +1,3 @@ -# see an image of this blueprint at: https://i.imgur.com/3pNc0HM.png "#dig start(16; 16; central 3x3 stairwell) 48 rooms, 4 tiles each (more aesthetic)" , , , , , , , , , , , , ,d, , , ,d, , , , , , , , , , , , , ,# , , , ,d, , , ,d, , , ,d,d,d,d,d,d,d, , , ,d, , , ,d, , , , ,# diff --git a/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv index 72df2258d..f9e97bc8d 100644 --- a/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv +++ b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv @@ -1,4 +1,3 @@ -# see an image of this blueprint at: https://i.imgur.com/ENi5QLX.png "#dig start(36;73) 97 rooms, 9 tiles each (fractal design)" , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.csv b/data/blueprints/library/tombs/The_Saracen_Crypts.csv index 5d66a88c3..ae6107894 100644 --- a/data/blueprints/library/tombs/The_Saracen_Crypts.csv +++ b/data/blueprints/library/tombs/The_Saracen_Crypts.csv @@ -1,4 +1,3 @@ -# see an image of this blueprint at https://i.imgur.com/Kcjvx6R.png #dig start(24;24) room for 513 corpses d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# From b123d96ad04e6c536b80a547dc08d3ff5e637e38 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 2 Nov 2020 03:27:05 -0800 Subject: [PATCH 072/112] add links to images back in --- .../library/bedrooms/28-3-Modified_Windmill_Villas.csv | 3 ++- .../library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv | 3 ++- .../blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv | 3 ++- data/blueprints/library/tombs/The_Saracen_Crypts.csv | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv index 006c01f55..89363e35f 100644 --- a/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv +++ b/data/blueprints/library/bedrooms/28-3-Modified_Windmill_Villas.csv @@ -1,4 +1,5 @@ -"#dig start(11; 11) 28 bedrooms, 3 tiles each (efficient layout)" +"#dig start(11; 12) 28 bedrooms, 3 tiles each (efficient layout)" +# see an image of this blueprint at https://i.imgur.com/XD7D4ux.png , , , , , ,d, , , , , ,d, , , , , , , , ,# , , , , , ,d, , , , , ,d, , , , , , , , ,# , , , ,d, ,d, ,d, ,d, ,d, ,d, , , , , , ,# diff --git a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv index 8906ae4ba..881be3dd1 100644 --- a/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv +++ b/data/blueprints/library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv @@ -1,4 +1,5 @@ -"#dig start(16; 16; central 3x3 stairwell) 48 rooms, 4 tiles each (more aesthetic)" +"#dig start(16; 17; central 3x3 stairwell) 48 rooms, 4 tiles each (more aesthetic)" +# see an image of this blueprint at: https://i.imgur.com/3pNc0HM.png , , , , , , , , , , , , ,d, , , ,d, , , , , , , , , , , , , ,# , , , ,d, , , ,d, , , ,d,d,d,d,d,d,d, , , ,d, , , ,d, , , , ,# , , ,d,d,d,d,d,d,d, , , ,d, ,d, ,d, , , ,d,d,d,d,d,d,d, , , ,# diff --git a/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv index f9e97bc8d..ec2a1ce1e 100644 --- a/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv +++ b/data/blueprints/library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv @@ -1,4 +1,5 @@ -"#dig start(36;73) 97 rooms, 9 tiles each (fractal design)" +"#dig start(36;74) 97 rooms, 9 tiles each (fractal design)" +# see an image of this blueprint at: https://i.imgur.com/ENi5QLX.png , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,d,d,d, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,# diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.csv b/data/blueprints/library/tombs/The_Saracen_Crypts.csv index ae6107894..a70b77a09 100644 --- a/data/blueprints/library/tombs/The_Saracen_Crypts.csv +++ b/data/blueprints/library/tombs/The_Saracen_Crypts.csv @@ -1,4 +1,5 @@ -#dig start(24;24) room for 513 corpses +#dig start(24;25) room for 513 corpses +# see an image of this blueprint at https://i.imgur.com/Kcjvx6R.png d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,d,d,d,d,d,d,d,d,d,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d, , , , , , , , , ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,# d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d, ,d,# From 1d0e2dc6bcaf18e4cac776dab4850918166939e3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 3 Nov 2020 14:56:16 -0500 Subject: [PATCH 073/112] Update extents handling to use building_extents_type --- library/modules/Buildings.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 6d6d777f7..8fd2b77a1 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -103,7 +103,7 @@ struct CoordHash { static unordered_map locationToBuilding; -static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) +static df::building_extents_type *getExtentTile(df::building_extents &extent, df::coord2d tile) { if (!extent.extents) return NULL; @@ -600,7 +600,7 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, for (int dy = 0; dy < size.y; dy++) { df::coord tile = pos + df::coord(dx,dy,0); - uint8_t *etile = NULL; + df::building_extents_type *etile = NULL; // Exclude using extents if (ext && ext->extents) @@ -640,7 +640,7 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, if (!ext->extents) { - ext->extents = new uint8_t[size.x*size.y]; + ext->extents = new df::building_extents_type[size.x*size.y]; ext->x = pos.x; ext->y = pos.y; ext->width = size.x; @@ -653,7 +653,7 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, if (!etile) return false; - *etile = 0; + *etile = df::building_extents_type::None; } } } @@ -710,7 +710,7 @@ bool Buildings::containsTile(df::building *bld, df::coord2d tile, bool room) if (bld->room.extents && (room || bld->isExtentShaped())) { - uint8_t *etile = getExtentTile(bld->room, tile); + df::building_extents_type *etile = getExtentTile(bld->room, tile); if (!etile || !*etile) return false; } @@ -842,7 +842,7 @@ static void markBuildingTiles(df::building *bld, bool remove) if (use_extents) { - uint8_t *etile = getExtentTile(bld->room, tile); + df::building_extents_type *etile = getExtentTile(bld->room, tile); if (!etile || !*etile) continue; } @@ -883,7 +883,7 @@ static void linkRooms(df::building *bld) if (!room->is_room || room->z != bld->z) continue; - uint8_t *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1)); + df::building_extents_type *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1)); if (!pext || !*pext) continue; From 3aa902bd256591d001cdc9a49485175de0a0e853 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 3 Nov 2020 19:52:24 -0500 Subject: [PATCH 074/112] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 00549aca0..72688ee86 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 00549aca0640ca121c20734df667f79b247c93b4 +Subproject commit 72688ee8651ad7ab1f056f7120b0052dc30c1d47 diff --git a/scripts b/scripts index 861b4349d..1c4f3dd51 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 861b4349dc7bc542465de3c168af3ca18561e0d6 +Subproject commit 1c4f3dd519349ab8140a0e7fa181df51253c43d2 From 276da1063d04945b67b08a2ce28f6300cd37a399 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 4 Nov 2020 21:23:46 -0500 Subject: [PATCH 075/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 1c4f3dd51..eed049ae9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1c4f3dd519349ab8140a0e7fa181df51253c43d2 +Subproject commit eed049ae92371d19e8362166851581293e21d79a From 4efb01142913d5f1d72878e8bb409f6353443b6d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 5 Nov 2020 16:01:44 -0800 Subject: [PATCH 076/112] quickfort guide: 'c' stockpiles don't make sense explain why "custom" stockpiles aren't meaningful to create in a blueprint and what the user should do instead --- docs/guides/quickfort-user-guide.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 964776f65..0df4bc24c 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -502,7 +502,12 @@ meeting area all at once: #zone main pasture and picnic area nmg(10x10) -The order of the individual letters doesn't matter. +The order of the individual letters doesn't matter. Note that "custom" +(:kbd:`c`) stockpiles cannot be declared in a blueprint since what would +get created would depend on what happens to be set in the "custom stockpile +settings" in your game. Instead, place stockpiles using the keys that +represent the types you want to store and then use a ``#query`` blueprint to +customize them. Detailed configuration for zones, such as the pit/pond toggle, can also be set by mimicking the hotkeys used to set them. Note that gather flags default to From edf55b2ae64a2965ea581436ec19751a8c407ca9 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 5 Nov 2020 16:11:27 -0800 Subject: [PATCH 077/112] use numbered lists in meta blueprints section --- docs/guides/quickfort-user-guide.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 0df4bc24c..f8061b766 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -897,13 +897,13 @@ Meta blueprints Meta blueprints are blueprints that script a series of other blueprints. Many blueprint packages follow this pattern: -- Apply dig blueprint to designate dig areas -- Wait for miners to dig -- **Apply build buildprint** to designate buildings -- **Apply place buildprint** to designate stockpiles -- **Apply query blueprint** to configure stockpiles -- Wait for buildings to get built -- Apply a different query blueprint to configure rooms +1. Apply dig blueprint to designate dig areas +#. Wait for miners to dig +#. **Apply build buildprint** to designate buildings +#. **Apply place buildprint** to designate stockpiles +#. **Apply query blueprint** to configure stockpiles +#. Wait for buildings to get built +#. Apply a different query blueprint to configure rooms Those three "apply"s in the middle might as well get done in one command instead of three. A meta blueprint can encode that sequence. A meta blueprint refers to @@ -947,12 +947,12 @@ blueprints into one: Now your sequence is shortened to: -- Apply dig blueprint to designate dig areas -- Wait for miners to dig -- **Apply meta buildprint** to build buildings and designate/configure - stockpiles -- Wait for buildings to get built -- Apply the final query blueprint to configure the room +1. Apply dig blueprint to designate dig areas +#. Wait for miners to dig +#. **Apply meta buildprint** to build buildings and designate/configure + stockpiles +#. Wait for buildings to get built +#. Apply the final query blueprint to configure the room You can use meta blueprints to lay out your fortress at a larger scale as well. The ``#<`` and ``#>`` notation is valid in meta blueprints, so you can, for From e8f4b4016b8dff8dc56c53db9a7eca2e126e35c2 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 5 Nov 2020 16:18:33 -0800 Subject: [PATCH 078/112] reinforce that meta blueprints can't cross files they must refer to labels that are within the same .xlsx or .csv file --- docs/guides/quickfort-user-guide.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index f8061b766..5ab0956f7 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1015,6 +1015,11 @@ a big fort, so we're planning for a lot of bedrooms): Note that for blueprints without an explicit label, we still need to address them by their auto-generated numerical label. +It's worth calling out that ``#meta`` blueprints can only refer to blueprints +that are defined in the same file. This means that all blueprints that a +``#meta`` blueprint needs to script must be in sheets within the same +.xlsx spreadsheet or concatenated into the same .csv file. + You can then hide the blueprints that you now manage with the ``#meta``-mode blueprint from ``quickfort list`` by adding a ``hidden()`` marker to their modelines. That way the output of ``quickfort list`` won't be cluttered by From aca2be7182fa49be83d5b7d9525a2936f65cff6e Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Nov 2020 23:41:43 -0500 Subject: [PATCH 079/112] Disable MSVC warnings in dependencies --- depends/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 566a4d57f..40bbd2aa3 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -42,6 +42,9 @@ set(LIBZIP_ENABLE_OPENSSL OFF CACHE BOOL "") set(LIBZIP_ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "") set(LIBZIP_DO_INSTALL OFF CACHE BOOL "") add_subdirectory(libzip) +if(MSVC) + target_compile_options(zip PRIVATE /wd4244) +endif() set(XLSXIO_USE_DFHACK_LIBS ON CACHE BOOL "") set(XLSXIO_BUILD_STATIC ON CACHE BOOL "") @@ -55,3 +58,6 @@ set(XLSXIO_LIBZIP_DIR "${CMAKE_CURRENT_BINARY_DIR}/libzip" CACHE PATH "") set(XLSXIO_EXPAT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libexpat" CACHE PATH "") set(XLSXIO_ENABLE_INSTALL OFF CACHE BOOL "") add_subdirectory(xlsxio) +if(MSVC) + target_compile_options(xlsxio_read_STATIC PRIVATE /wd4013 /wd4244) +endif() From eee33822e7aac4ae4fc88800581aaafa00d016fc Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Nov 2020 23:41:53 -0500 Subject: [PATCH 080/112] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 72688ee86..9eb93c91c 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 72688ee8651ad7ab1f056f7120b0052dc30c1d47 +Subproject commit 9eb93c91c8c69df0860fb9a896742d17a0472f59 diff --git a/scripts b/scripts index eed049ae9..a2b0c7c41 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit eed049ae92371d19e8362166851581293e21d79a +Subproject commit a2b0c7c41919b54933858482f1daa1dae6a6664b From 17432072f247bc0c1e818583c01154a85cd4bdc5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 9 Nov 2020 23:48:57 -0500 Subject: [PATCH 081/112] Also apply to xlsxio_write --- depends/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 40bbd2aa3..3d7e18da7 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -60,4 +60,5 @@ set(XLSXIO_ENABLE_INSTALL OFF CACHE BOOL "") add_subdirectory(xlsxio) if(MSVC) target_compile_options(xlsxio_read_STATIC PRIVATE /wd4013 /wd4244) + target_compile_options(xlsxio_write_STATIC PRIVATE /wd4013 /wd4244) endif() From 0958fdbf4b38791468f69e590793bf9f512a6626 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 10 Nov 2020 00:48:27 -0500 Subject: [PATCH 082/112] Document script paths Ref #1690 --- docs/Core.rst | 48 ++++++++++++++++++++++++++- docs/Lua API.rst | 86 ++++++++++++++++++++++++++++++------------------ 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/docs/Core.rst b/docs/Core.rst index f18adfdc1..4fdecbf63 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -454,9 +454,55 @@ Other init files directory, will be run when any world or that save is loaded. +.. _dfhack-config: + +Configuration Files +=================== + +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: + +Script paths +------------ + +Script paths are folders that DFHack searches to find a script when a command is +run. By default, the following folders are searched, in order (relative to the +root DF folder): + +1. :file:`data/save/{}/raw/scripts` (only if a save is loaded) +2. :file:`raw/scripts` +3. :file:`hack/scripts` + +For example, if ``teleport`` is run, these folders are searched in order for +``teleport.lua`` or ``teleport.rb``, and the first matching file is run. + +Script paths can be added by modifying :file:`dfhack-config/script-paths.txt`. +Each line should start with one of these characters: + +- ``+``: adds a script path that is searched *before* the default paths (above) +- ``-``: adds a script path that is searched *after* the default paths +- ``#``: a comment (the line is ignored) + +Paths can be absolute or relative - relative paths are interpreted relative to +the root DF folder. + +.. admonition:: Tip + + When developing scripts in the :source:scripts:`dfhack/scripts repo <>`, + it may be useful to add the path to your local copy of the repo with ``+``. + This will allow you to make changes in the repo and have them take effect + immediately, without needing to re-install or copy scripts over manually. + + +Script paths can also be modified programmatically through the `Lua API `. + .. _env-vars: -Environment variables +Environment Variables ===================== DFHack's behavior can be adjusted with some environment variables. For example, diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 4c6f0d6fa..ffe705d60 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -2213,6 +2213,8 @@ Console API Flushes all output to the console. This can be useful when printing text that does not end in a newline but should still be displayed. +.. _lua-api-internal: + Internal API ------------ @@ -2311,7 +2313,7 @@ and are only documented here for completeness: * ``dfhack.internal.addScriptPath(path, search_before)`` - Adds ``path`` to the list of paths searched for scripts (both in Lua and Ruby). + Registers ``path`` as a `script path `. If ``search_before`` is passed and ``true``, the path will be searched before the default paths (e.g. ``raw/scripts``, ``hack/scripts``); otherwise, it will be searched after. @@ -2321,17 +2323,18 @@ and are only documented here for completeness: * ``dfhack.internal.removeScriptPath(path)`` - Removes ``path`` from the script search paths and returns ``true`` if successful. + Removes ``path`` from the list of `script paths ` and returns + ``true`` if successful. * ``dfhack.internal.getScriptPaths()`` - Returns the list of script paths in the order they are searched, including defaults. - (This can change if a world is loaded.) + Returns the list of `script paths ` in the order they are + searched, including defaults. (This can change if a world is loaded.) * ``dfhack.internal.findScript(name)`` - Searches script paths for the script ``name`` and returns the path of the first - file found, or ``nil`` on failure. + Searches `script paths ` for the script ``name`` and returns the + path of the first file found, or ``nil`` on failure. .. note:: This requires an extension to be specified (``.lua`` or ``.rb``) - use @@ -4280,35 +4283,54 @@ Scripts .. contents:: :local: -Any files with the .lua extension placed into :file:`hack/scripts/*` -are automatically used by the DFHack core as commands. The -matching command name consists of the name of the file without -the extension. First DFHack searches for the script in the :file:`/raw/scripts/` folder. If it is not found there, it searches in the :file:`/raw/scripts/` folder. If it is not there, it searches in -:file:`/hack/scripts/`. If it is not there, it gives up. +Any files with the ``.lua`` extension placed into the :file:`hack/scripts` folder +are automatically made avaiable as DFHack commands. The command corresponding to +a script is simply the script's filename, relative to the scripts folder, with +the extension omitted. For example: -If the first line of the script is a one-line comment, it is -used by the built-in ``ls`` and ``help`` commands. -Such a comment is required for every script in the official DFHack repository. +* :file:`hack/scripts/add-thought.lua` is invoked as ``add-thought`` +* :file:`hack/scripts/gui/teleport.lua` is invoked as ``gui/teleport`` .. note:: - Scripts placed in subdirectories still can be accessed, but - do not clutter the `ls` command list (unless ``ls -a``; thus it is preferred - for obscure developer-oriented scripts and scripts used by tools. - When calling such scripts, always use '/' as the separator for - directories, e.g. ``devel/lua-example``. - -Scripts are re-read from disk if they have changed since the last time they were read. -Global variable values persist in memory between calls, unless the file has changed. -Every script gets its own separate environment for global -variables. - -Arguments are passed in to the scripts via the **...** built-in -quasi-variable; when the script is called by the DFHack core, -they are all guaranteed to be non-nil strings. - -DFHack core invokes the scripts in the `core context `; -however it is possible to call them from any lua code (including -from other scripts) in any context, via the same function the core uses: + Scripts placed in subdirectories can be run as described above, but are not + listed by the `ls` command unless ``-a`` is specified. In general, scripts + should be placed in subfolders in the following situations: + + * ``devel``: scripts that are intended exclusively for DFHack development, + including examples, or scripts that are experimental and unstable + * ``fix``: fixes for specific DF issues + * ``gui``: GUI front-ends for existing tools (for example, see the + relationship between `teleport` and `gui/teleport`) + * ``modtools``: scripts that are intended to be run exclusively as part of + mods, not directly by end-users (as a rule of thumb: if someone other than + a mod developer would want to run a script from the console, it should + not be placed in this folder) + +Scripts can also be placed in other folders - by default, these include +:file:`raw/scripts` and :file:`data/save/{region}/raw/scripts`, but additional +folders can be added (for example, a copy of the +:source:scripts:`scripts repository <>` for local development). See +`script-paths` for more information on how to configure this behavior. + +If the first line of the script is a one-line comment (starting with ``--``), +the content of the comment is used by the built-in ``ls`` and ``help`` commands. +Such a comment is required for every script in the official DFHack repository. + +Scripts are read from disk when run for the first time, or if they have changed +since the last time they were run. + +Each script has an isolated environment where global variables set by the script +are stored. Values of globals persist across script runs in the same DF session. +See `devel/lua-example` for an example of this behavior. Note that local +variables do *not* persist. + +Arguments are passed in to the scripts via the ``...`` built-in quasi-variable; +when the script is called by the DFHack core, they are all guaranteed to be +non-nil strings. + +DFHack invokes the scripts in the `core context `; however it +is possible to call them from any lua code (including from other scripts) in any +context, via the same function the core uses: * ``dfhack.run_script(name[,args...])`` From c87d5260d3a3ffa9de70c7d96b1ff889a21d95a5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 11 Nov 2020 16:04:40 -0500 Subject: [PATCH 083/112] buildingplan: make hotkeys added to existing menus red For clarity (per Lua API.rst guidelines) --- plugins/buildingplan.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 1ce99ea0d..c23d15574 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -505,9 +505,9 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest ++y; if (hasPrevFilter()) - OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, left_margin); + OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); if (hasNextFilter()) - OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, left_margin); + OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); } }; @@ -562,7 +562,7 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest show_help = false; return false; } - + initStatics(); if (in_dummy_screen) @@ -724,8 +724,10 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); } - OutputToggleString(x, y, "Planning Mode", "P", planmode_enabled[key], true, left_margin); - OutputHotkeyString(x, y, "Global Settings", "G", true, left_margin); + OutputToggleString(x, y, "Planning Mode", interface_key::CUSTOM_SHIFT_P, + planmode_enabled[key], true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); + OutputHotkeyString(x, y, "Global Settings", interface_key::CUSTOM_SHIFT_G, + true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); if (!is_planmode_enabled(key)) return; @@ -739,16 +741,18 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest if (item_can_be_improved(key, filter_idx)) { - OutputHotkeyString(x, y, "Min Quality: ", "QW"); + OutputHotkeyString(x, y, "Min Quality: ", "QW", false, 0, COLOR_WHITE, COLOR_LIGHTRED); OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); - OutputHotkeyString(x, y, "Max Quality: ", "AS"); + OutputHotkeyString(x, y, "Max Quality: ", "AS", false, 0, COLOR_WHITE, COLOR_LIGHTRED); OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); - OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin); + OutputToggleString(x, y, "Decorated Only", interface_key::CUSTOM_SHIFT_D, + filter->getDecoratedOnly(), true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); } - OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin); + OutputHotkeyString(x, y, "Material Filter:", interface_key::CUSTOM_SHIFT_M, true, + left_margin, COLOR_WHITE, COLOR_LIGHTRED); auto filter_descriptions = filter->getMaterials(); for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it) @@ -756,9 +760,11 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest y += 2; if (hasPrevFilter()) - OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, left_margin); + OutputHotkeyString(x, y, "Prev Item", "Ctrl+Left", true, + left_margin, COLOR_WHITE, COLOR_LIGHTRED); if (hasNextFilter()) - OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, left_margin); + OutputHotkeyString(x, y, "Next Item", "Ctrl+Right", true, + left_margin, COLOR_WHITE, COLOR_LIGHTRED); } }; From 421844dcb866e693b244b21eebdbe3a114ecdde9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 11 Nov 2020 16:05:24 -0500 Subject: [PATCH 084/112] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9eb93c91c..dd8b93350 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9eb93c91c8c69df0860fb9a896742d17a0472f59 +Subproject commit dd8b9335007941c3aea27474b0a08ba31f6bc97f From 2ec8dd12253aeb99e45fe48085d9e0fbd0cf37a9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 11 Nov 2020 16:53:29 -0800 Subject: [PATCH 085/112] update quickfort guide and aliases - move bit of alias documentation from aliases-common to aliases.txt - document new behavior for 'c'ustom stockpiles --- data/quickfort/aliases-common.txt | 7 +++---- dfhack-config/quickfort/aliases.txt | 6 +++++- docs/guides/quickfort-user-guide.rst | 11 +++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index 857160711..59705f9f7 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -17,10 +17,6 @@ # # Aliases that don't fit into those two categories have comments explaining # their usage. -# -# There is also a non-alphanumeric alias built into the code for the common -# shorthand for "make room": -# r+ expands to r+& ######################################## @@ -41,6 +37,9 @@ give10down: {give move={Down 10}} give10left: {give move={Left 10}} give10right: {give move={Right 10}} +# usage example: {givename name="myname"} +givename: {Ctrl}n{name}& + # use to toggle a sequence of stockpile options. for example: {togglesequence 5} togglesequence: &{Down} togglesequence2: &{Down 2} diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt index 2743a39f3..9be32107e 100644 --- a/dfhack-config/quickfort/aliases.txt +++ b/dfhack-config/quickfort/aliases.txt @@ -95,8 +95,12 @@ # ! expands to {Ctrl} # ^ expands to {ESC} # +# There is also a non-standard alias built into the code for the common +# shorthand for "make room": +# r+ expands to r+& +# # If you need literal verisons of the shorthand characters, surround them in -# curly brackets, for example: "{~}" +# curly brackets, for example: "{!}" # # # Add your custom aliases here: diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 5ab0956f7..1d9c1a024 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -502,12 +502,11 @@ meeting area all at once: #zone main pasture and picnic area nmg(10x10) -The order of the individual letters doesn't matter. Note that "custom" -(:kbd:`c`) stockpiles cannot be declared in a blueprint since what would -get created would depend on what happens to be set in the "custom stockpile -settings" in your game. Instead, place stockpiles using the keys that -represent the types you want to store and then use a ``#query`` blueprint to -customize them. +The order of the individual letters doesn't matter. If you want to configure the +stockpile from scratch in a ``#query`` blueprint, you can place unconfigured +"custom" stockpiles with (:kbd:`c`). It is more efficient, though, to place +stockpiles using the keys that represent the types you want to store, and +then only use a ``#query`` blueprint if you need fine-grained customization. Detailed configuration for zones, such as the pit/pond toggle, can also be set by mimicking the hotkeys used to set them. Note that gather flags default to From 54702085d550a8a5183523e61b8c939bd29cde1f Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 11 Nov 2020 17:12:56 -0800 Subject: [PATCH 086/112] don't eat keys while building is being renamed allows buildingplan to prevent unsuspension of planned buildings without also eating the 's' key when the user is trying to use it to give the building a custom name. --- plugins/buildingplan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index c23d15574..fadd723a7 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -431,7 +431,7 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest bool handleInput(set *input) { - if (!isInPlannedBuildingQueryMode()) + if (!isInPlannedBuildingQueryMode() || Gui::inRenameBuilding()) return false; initStatics(); From 36110902fa3148904cb108f0ed51f9477dc11024 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 00:11:13 -0800 Subject: [PATCH 087/112] don't use cached iterators across map reloads --- plugins/buildingplan.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 2d1785579..dedc89f62 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -457,6 +457,11 @@ struct buildingplan_query_hook : public df::viewscreen_dwarfmodest } } + static void invalidateStatics() + { + bld = NULL; + } + bool handleInput(set *input) { if (!isInPlannedBuildingQueryMode() || Gui::inRenameBuilding()) @@ -583,6 +588,11 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest } } + static void invalidateStatics() + { + key = BuildingTypeKey(); + } + bool handleInput(set *input) { if (!isInPlannedBuildingPlacementMode()) @@ -959,6 +969,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { switch (event) { case SC_MAP_LOADED: + buildingplan_place_hook::invalidateStatics(); + buildingplan_query_hook::invalidateStatics(); planner.reset(); roomMonitor.reset(out); break; From 913d860ae47bd8b0e8b24db8520bc33dfd8815d4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 12 Nov 2020 19:07:51 -0500 Subject: [PATCH 088/112] Use initial working directory as process path on Linux, and expose to Lua --- docs/Lua API.rst | 6 +++- library/LuaApi.cpp | 3 +- library/Process-linux.cpp | 48 ++++++++-------------------- library/include/modules/Filesystem.h | 3 +- library/modules/Filesystem.cpp | 9 +++++- 5 files changed, 30 insertions(+), 39 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 5939f50cb..80fd0add6 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -2160,10 +2160,14 @@ unless otherwise noted. Changes the current directory to ``path``. Use with caution. -* ``dfhack.filesystem.restorecwd()`` +* ``dfhack.filesystem.restore_cwd()`` Restores the current working directory to what it was when DF started. +* ``dfhack.filesystem.get_initial_cwd()`` + + Returns the value of the working directory when DF was started. + * ``dfhack.filesystem.mkdir(path)`` Creates a new directory. Returns ``false`` if unsuccessful, including if ``path`` already exists. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d38cb31ba..431ab3ae5 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2371,7 +2371,8 @@ static const luaL_Reg dfhack_screen_funcs[] = { static const LuaWrapper::FunctionReg dfhack_filesystem_module[] = { WRAPM(Filesystem, getcwd), - WRAPM(Filesystem, restorecwd), + WRAPM(Filesystem, restore_cwd), + WRAPM(Filesystem, get_initial_cwd), WRAPM(Filesystem, chdir), WRAPM(Filesystem, mkdir), WRAPM(Filesystem, mkdir_recursive), diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 45e655948..be3ebb5df 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -22,29 +22,28 @@ must not be misrepresented as being the original software. distribution. */ -#include "Internal.h" - +#include +#include #include #include +#include +#include +#include #include #include #include - -#include #include -#include -#include -#include -#include -using namespace std; -#include +#include "Error.h" +#include "Internal.h" +#include "md5wrapper.h" #include "MemAccess.h" #include "Memory.h" -#include "VersionInfoFactory.h" +#include "modules/Filesystem.h" #include "VersionInfo.h" -#include "Error.h" -#include +#include "VersionInfoFactory.h" + +using namespace std; using namespace DFHack; Process::Process(const VersionInfoFactory& known_versions) : identified(false), my_pe(0) @@ -181,28 +180,7 @@ uint32_t Process::getTickCount() string Process::getPath() { - static string cached_path; - if (cached_path.empty()) - { - const char *exe_name = "/proc/self/exe"; - char exe_path[1024]; - int length = readlink(exe_name, exe_path, sizeof(exe_path)); - if (length > 0) - { - exe_path[length] = '\0'; - string path_string = exe_path; - // DF lives in libs, so move up a folder - cached_path = path_string.substr(0, path_string.find_last_of("/", path_string.find_last_of("/") - 1)); - } - else - { - perror("readlink(/proc/self/exe) failed"); - fprintf(stderr, " length=%i\n", length); - cached_path = "."; - } - fprintf(stderr, "Resolved DF root to %s\n", cached_path.c_str()); - } - return cached_path; + return Filesystem::get_initial_cwd(); } int Process::getPID() diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h index eac8a1102..8e7bab9b2 100644 --- a/library/include/modules/Filesystem.h +++ b/library/include/modules/Filesystem.h @@ -149,7 +149,8 @@ namespace DFHack { DFHACK_EXPORT void init (); DFHACK_EXPORT bool chdir (std::string path); DFHACK_EXPORT std::string getcwd (); - DFHACK_EXPORT bool restorecwd (); + DFHACK_EXPORT bool restore_cwd (); + DFHACK_EXPORT std::string get_initial_cwd (); DFHACK_EXPORT bool mkdir (std::string path); // returns true on success or if directory already exists DFHACK_EXPORT bool mkdir_recursive (std::string path); diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 7738bfd5e..e0d0bc8c2 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -66,6 +66,7 @@ void Filesystem::init () bool Filesystem::chdir (std::string path) { + Filesystem::init(); return ::chdir(path.c_str()) == 0; } @@ -83,11 +84,17 @@ std::string Filesystem::getcwd () return result; } -bool Filesystem::restorecwd () +bool Filesystem::restore_cwd () { return Filesystem::chdir(initial_cwd); } +std::string Filesystem::get_initial_cwd () +{ + Filesystem::init(); + return initial_cwd; +} + bool Filesystem::mkdir (std::string path) { int fail; From b55d844164fd2bfdc9c1088e0a356cdd6a1f3bde Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 12 Nov 2020 21:03:05 -0500 Subject: [PATCH 089/112] Add test for get_initial_cwd() --- test/core.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/core.lua b/test/core.lua index 6cde334d4..ba104c90a 100644 --- a/test/core.lua +++ b/test/core.lua @@ -13,6 +13,10 @@ function test.getDFPath() expect.eq(clean_path(dfhack.getDFPath()), old_cwd) end +function test.get_initial_cwd() + expect.eq(clean_path(dfhack.filesystem.get_initial_cwd()), clean_path(dfhack.getDFPath())) +end + function test.getDFPath_chdir() dfhack.with_finalize(restore_cwd, function() fs.chdir('data') From a5e58b766bb83b9530254cfa5b7ed6098f8247f2 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 12 Nov 2020 21:07:14 -0500 Subject: [PATCH 090/112] Update scripts, changelog --- docs/changelog.txt | 1 + scripts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 79179ce5e..9cbecf9ec 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,6 +34,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## Fixes +- Fixed an issue on some Linux systems where DFHack installed through a package manager would attempt to write files to a non-writable folder (notably when running `exportlegends` or `gui/autogems`) - `buildingplan`: artifacts are now successfully matched when max quality is set to ``artifacts`` - `buildingplan`: no longer erroneously matches items to buildings while the game is paused - `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences diff --git a/scripts b/scripts index a2b0c7c41..ccf1d035a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a2b0c7c41919b54933858482f1daa1dae6a6664b +Subproject commit ccf1d035a653c991b669fccc8293ecbab449548f From 02a86d761aa51e6ec54d179f931fc57b073a2765 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 19:10:06 -0800 Subject: [PATCH 091/112] identify bars as non_economic items allows Job::isSuitableMaterial() to match metal bars as a building material --- library/include/modules/Job.h | 2 +- library/include/modules/Materials.h | 5 +++-- library/modules/Job.cpp | 5 +++-- library/modules/Materials.cpp | 9 ++++++--- plugins/buildingplan-planner.cpp | 3 ++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index cde5c64dd..4312b7b4e 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -102,7 +102,7 @@ namespace DFHack int filter_idx = -1, int insert_idx = -1); DFHACK_EXPORT bool isSuitableItem(df::job_item *item, df::item_type itype, int isubtype); - DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index); + DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index, df::item_type itype = df::item_type::NONE); DFHACK_EXPORT std::string getName(df::job *job); } diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index a3b4204e5..b438a0cdb 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -78,6 +78,7 @@ namespace DFHack int16_t type; int32_t index; + df::item_type itype; df::material *material; @@ -98,7 +99,7 @@ namespace DFHack df::historical_figure *figure; public: - MaterialInfo(int16_t type = -1, int32_t index = -1) { decode(type, index); } + MaterialInfo(int16_t type = -1, int32_t index = -1, df::item_type itype = df::item_type::NONE) { decode(type, index, itype); } MaterialInfo(const t_matpair &mp) { decode(mp.mat_type, mp.mat_index); } template MaterialInfo(T *ptr) { decode(ptr); } @@ -113,7 +114,7 @@ namespace DFHack bool isAnyInorganic() const { return type == 0; } bool isInorganicWildcard() const { return isAnyInorganic() && isBuiltin(); } - bool decode(int16_t type, int32_t index = -1); + bool decode(int16_t type, int32_t index = -1, df::item_type itype = df::item_type::NONE); bool decode(df::item *item); bool decode(const df::material_vec_ref &vr, int idx); bool decode(const t_matpair &mp) { return decode(mp.mat_type, mp.mat_index); } diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 0aa01af95..9e04e1a30 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -608,7 +608,8 @@ bool Job::isSuitableItem(df::job_item *item, df::item_type itype, int isubtype) return iinfo.isValid() && iinfo.matches(*item, &minfo); } -bool Job::isSuitableMaterial(df::job_item *item, int mat_type, int mat_index) +bool Job::isSuitableMaterial( + df::job_item *item, int mat_type, int mat_index, df::item_type itype) { CHECK_NULL_POINTER(item); @@ -616,7 +617,7 @@ bool Job::isSuitableMaterial(df::job_item *item, int mat_type, int mat_index) return true; ItemTypeInfo iinfo(item); - MaterialInfo minfo(mat_type, mat_index); + MaterialInfo minfo(mat_type, mat_index, itype); return minfo.isValid() && iinfo.matches(*item, &minfo); } diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 909fa56f9..e3a4bf23f 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -75,7 +75,9 @@ bool MaterialInfo::decode(df::item *item) if (!item) return decode(-1); else - return decode(item->getActualMaterial(), item->getActualMaterialIndex()); + return decode(item->getActualMaterial(), + item->getActualMaterialIndex(), + item->getType()); } bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) @@ -86,10 +88,11 @@ bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) return decode(vr.mat_type[idx], vr.mat_index[idx]); } -bool MaterialInfo::decode(int16_t type, int32_t index) +bool MaterialInfo::decode(int16_t type, int32_t index, df::item_type itype) { this->type = type; this->index = index; + this->itype = itype; material = NULL; mode = Builtin; subtype = 0; @@ -513,7 +516,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(fire_safe, material->heat.melting_point > 11000); TEST(magma_safe, material->heat.melting_point > 12000); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); - TEST(non_economic, !inorganic || !(ui && vector_get(ui->economic_stone, index))); + TEST(non_economic, !inorganic || !(ui && vector_get(ui->economic_stone, index)) || itype == df::item_type::BAR); TEST(plant, plant); TEST(silk, MAT_FLAG(SILK)); diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 834465275..7aad342e5 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -825,7 +825,8 @@ static bool matchesFilters(df::item * item, return DFHack::Job::isSuitableItem( job_item, item->getType(), item->getSubtype()) && DFHack::Job::isSuitableMaterial( - job_item, item->getMaterial(), item->getMaterialIndex()) + job_item, item->getMaterial(), item->getMaterialIndex(), + item->getType()) && item_filter.matches(item); } From 6b14a92385535027dc15983ba37ca402d3ded066 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 19:54:00 -0800 Subject: [PATCH 092/112] allow buildingplan to match metal bars --- plugins/buildingplan-planner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index 7aad342e5..b06ce7f84 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -224,7 +224,7 @@ bool ItemFilter::matches(df::item *item) const auto imattype = item->getActualMaterial(); auto imatindex = item->getActualMaterialIndex(); - auto item_mat = DFHack::MaterialInfo(imattype, imatindex); + auto item_mat = DFHack::MaterialInfo(imattype, imatindex, item->getType()); return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); } From 10616a387fcc8b5351e7cb24fbb5ee11caed99a0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 22:44:38 -0800 Subject: [PATCH 093/112] cleaner mask-based implementation --- library/include/modules/Items.h | 4 +++- library/include/modules/Job.h | 4 +++- library/include/modules/Materials.h | 8 ++++---- library/modules/Items.cpp | 5 +++-- library/modules/Job.cpp | 6 +++--- library/modules/Materials.cpp | 16 ++++++++-------- plugins/buildingplan-planner.cpp | 2 +- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 33feea222..66ca9d1cf 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -88,7 +88,9 @@ namespace DFHack bool find(const std::string &token); bool matches(df::job_item_vector_id vec_id); - bool matches(const df::job_item &item, MaterialInfo *mat = NULL, bool skip_vector = false); + bool matches(const df::job_item &item, MaterialInfo *mat = NULL, + bool skip_vector = false, + df::item_type itype = df::item_type::NONE); }; inline bool operator== (const ItemTypeInfo &a, const ItemTypeInfo &b) { diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 4312b7b4e..e2d7ff162 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -102,7 +102,9 @@ namespace DFHack int filter_idx = -1, int insert_idx = -1); DFHACK_EXPORT bool isSuitableItem(df::job_item *item, df::item_type itype, int isubtype); - DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index, df::item_type itype = df::item_type::NONE); + DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, + int mat_index, + df::item_type itype); DFHACK_EXPORT std::string getName(df::job *job); } diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index b438a0cdb..3217b30ad 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -78,7 +78,6 @@ namespace DFHack int16_t type; int32_t index; - df::item_type itype; df::material *material; @@ -99,7 +98,7 @@ namespace DFHack df::historical_figure *figure; public: - MaterialInfo(int16_t type = -1, int32_t index = -1, df::item_type itype = df::item_type::NONE) { decode(type, index, itype); } + MaterialInfo(int16_t type = -1, int32_t index = -1) { decode(type, index); } MaterialInfo(const t_matpair &mp) { decode(mp.mat_type, mp.mat_index); } template MaterialInfo(T *ptr) { decode(ptr); } @@ -114,7 +113,7 @@ namespace DFHack bool isAnyInorganic() const { return type == 0; } bool isInorganicWildcard() const { return isAnyInorganic() && isBuiltin(); } - bool decode(int16_t type, int32_t index = -1, df::item_type itype = df::item_type::NONE); + bool decode(int16_t type, int32_t index = -1); bool decode(df::item *item); bool decode(const df::material_vec_ref &vr, int idx); bool decode(const t_matpair &mp) { return decode(mp.mat_type, mp.mat_index); } @@ -157,7 +156,8 @@ namespace DFHack bool matches(const df::job_material_category &cat); bool matches(const df::dfhack_material_category &cat); - bool matches(const df::job_item &item); + bool matches(const df::job_item &item, + df::item_type itype = df::item_type::NONE); }; DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category *cat, const std::string &token); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 3bc468d04..fa8607638 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -279,12 +279,13 @@ bool ItemTypeInfo::matches(df::job_item_vector_id vec_id) return true; } -bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool skip_vector) +bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, + bool skip_vector, df::item_type itype) { using namespace df::enums::item_type; if (!isValid()) - return mat ? mat->matches(item) : false; + return mat ? mat->matches(item, itype) : false; if (Items::isCasteMaterial(type) && mat && !mat->isNone()) return false; diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 9e04e1a30..366384fd2 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -605,7 +605,7 @@ bool Job::isSuitableItem(df::job_item *item, df::item_type itype, int isubtype) ItemTypeInfo iinfo(itype, isubtype); MaterialInfo minfo(item); - return iinfo.isValid() && iinfo.matches(*item, &minfo); + return iinfo.isValid() && iinfo.matches(*item, &minfo, true, itype); } bool Job::isSuitableMaterial( @@ -617,9 +617,9 @@ bool Job::isSuitableMaterial( return true; ItemTypeInfo iinfo(item); - MaterialInfo minfo(mat_type, mat_index, itype); + MaterialInfo minfo(mat_type, mat_index); - return minfo.isValid() && iinfo.matches(*item, &minfo); + return minfo.isValid() && iinfo.matches(*item, &minfo, true, itype); } std::string Job::getName(df::job *job) diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index e3a4bf23f..56a13fbe2 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -76,8 +76,7 @@ bool MaterialInfo::decode(df::item *item) return decode(-1); else return decode(item->getActualMaterial(), - item->getActualMaterialIndex(), - item->getType()); + item->getActualMaterialIndex()); } bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) @@ -88,11 +87,10 @@ bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) return decode(vr.mat_type[idx], vr.mat_index[idx]); } -bool MaterialInfo::decode(int16_t type, int32_t index, df::item_type itype) +bool MaterialInfo::decode(int16_t type, int32_t index) { this->type = type; this->index = index; - this->itype = itype; material = NULL; mode = Builtin; subtype = 0; @@ -447,19 +445,22 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) #undef TEST -bool MaterialInfo::matches(const df::job_item &item) +bool MaterialInfo::matches(const df::job_item &item, df::item_type itype) { if (!isValid()) return false; df::job_item_flags1 ok1, mask1; getMatchBits(ok1, mask1); - df::job_item_flags2 ok2, mask2; + df::job_item_flags2 ok2, mask2, xmask2; getMatchBits(ok2, mask2); df::job_item_flags3 ok3, mask3; getMatchBits(ok3, mask3); + xmask2.bits.non_economic = itype != df::item_type::BOULDER; + mask2.whole &= ~xmask2.whole; + return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && bits_match(item.flags2.whole, ok2.whole, mask2.whole) && bits_match(item.flags3.whole, ok3.whole, mask3.whole); @@ -516,8 +517,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(fire_safe, material->heat.melting_point > 11000); TEST(magma_safe, material->heat.melting_point > 12000); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); - TEST(non_economic, !inorganic || !(ui && vector_get(ui->economic_stone, index)) || itype == df::item_type::BAR); - + TEST(non_economic, !inorganic || !(ui && vector_get(ui->economic_stone, index))); TEST(plant, plant); TEST(silk, MAT_FLAG(SILK)); TEST(leather, MAT_FLAG(LEATHER)); diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp index b06ce7f84..7aad342e5 100644 --- a/plugins/buildingplan-planner.cpp +++ b/plugins/buildingplan-planner.cpp @@ -224,7 +224,7 @@ bool ItemFilter::matches(df::item *item) const auto imattype = item->getActualMaterial(); auto imatindex = item->getActualMaterialIndex(); - auto item_mat = DFHack::MaterialInfo(imattype, imatindex, item->getType()); + auto item_mat = DFHack::MaterialInfo(imattype, imatindex); return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); } From fa126af1fda83a3aa04c73857f0407506cd121bb Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 22:49:06 -0800 Subject: [PATCH 094/112] undo unnecessary edits --- library/modules/Materials.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 56a13fbe2..91bb5dc36 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -75,8 +75,7 @@ bool MaterialInfo::decode(df::item *item) if (!item) return decode(-1); else - return decode(item->getActualMaterial(), - item->getActualMaterialIndex()); + return decode(item->getActualMaterial(), item->getActualMaterialIndex()); } bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) @@ -518,6 +517,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(magma_safe, material->heat.melting_point > 12000); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); TEST(non_economic, !inorganic || !(ui && vector_get(ui->economic_stone, index))); + TEST(plant, plant); TEST(silk, MAT_FLAG(SILK)); TEST(leather, MAT_FLAG(LEATHER)); From 93d9ac76be4d50654c0bc98a4869e8eef9a4d6eb Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 23:08:22 -0800 Subject: [PATCH 095/112] update docs --- docs/Lua API.rst | 2 +- docs/changelog.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index e017e0a8c..24887cd58 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1132,7 +1132,7 @@ Job module Does basic sanity checks to verify if the suggested item type matches the flags in the job item. -* ``dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)`` +* ``dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index, item_type)`` Likewise, if replacing material. diff --git a/docs/changelog.txt b/docs/changelog.txt index 79179ce5e..ac2e6b55f 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: ## API - `buildingplan`: added Lua interface API +- `job-module`: add item type param to dfhack.job.isSuitableMaterial() so the non_economic flag can be properly handled (it was being matched for all item types instead of just boulders) # 0.47.04-r3 From 54eec5d47fa4e0bf962bba92ffec0dd3957be523 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 23:11:55 -0800 Subject: [PATCH 096/112] there is no job-module -- it's just an html anchor --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index ac2e6b55f..4cb325f49 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -50,7 +50,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - `buildingplan`: added Lua interface API -- `job-module`: add item type param to dfhack.job.isSuitableMaterial() so the non_economic flag can be properly handled (it was being matched for all item types instead of just boulders) +- add item type param to dfhack.job.isSuitableMaterial() so the non_economic flag can be properly handled (it was being matched for all item types instead of just boulders) # 0.47.04-r3 From 625f003bc69ce53f2031dc6805a6fa3e08318b9b Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 23:20:53 -0800 Subject: [PATCH 097/112] add names for quantums and zones --- data/quickfort/aliases-common.txt | 15 +++++++++++++-- dfhack-config/quickfort/aliases.txt | 12 ++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index 59705f9f7..e4d2ef1cd 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -37,7 +37,8 @@ give10down: {give move={Down 10}} give10left: {give move={Left 10}} give10right: {give move={Right 10}} -# usage example: {givename name="myname"} +# Keep in mind that building, stockpile, and zone names have a maximum length +# of 20 characters. usage example: {givename name="myname"} givename: {Ctrl}n{name}& # use to toggle a sequence of stockpile options. for example: {togglesequence 5} @@ -49,7 +50,9 @@ enablesequence: e{Down} # clothes and armor in this quantum stockpile will rot away. If you want bones # in your quantum stockpile, apply this alias to a refuse stockpile (but don't # put useful clothes or armor in there!) -quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{enablefurniture}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}{enableweapons}{enablearmor}{enablesheet} +# Optionally set a name for the stockpile by specifying the 'name' parameter, +# for example: {quantum name="my name"} +quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{enablefurniture}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}{enableweapons}{enablearmor}{enablesheet}{givename} ################################## @@ -84,6 +87,14 @@ quantumstopfromwest: {quantumstopprefix}s{Left}p^{Right}{quantumstopsuffix} quantumstopfromnorth: {quantumstopprefix}s{Up}p^{Down}{quantumstopsuffix} +################################## +# zone aliases +################################## + +# usage example: {namezone name="my zone name"} +namezone: ^i{givename}^q + + ################################## # animal stockpile adjustments ################################## diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt index 9be32107e..1d1ebff9f 100644 --- a/dfhack-config/quickfort/aliases.txt +++ b/dfhack-config/quickfort/aliases.txt @@ -58,12 +58,12 @@ # The syntax for defining aliases is: # aliasname: keystrokes # -# Where aliasname is at least two letters or digits long and keystrokes are -# whatever you would type into the DF UI. A keystroke can also be a named -# keycode from the DF interface definition file (data/init/interface.txt), -# enclosed in curly brackets like an alias, like: "{Right}" or "{Enter}". In -# order to avoid naming conflicts between aliases and keycodes, the convention -# is to start aliases with a lowercase letter. +# Where aliasname is at least two letters or digits long (including dashes and +# underscores) and keystrokes are whatever you would type into the DF UI. A +# keystroke can also be a named keycode from the DF interface definition file +# (data/init/interface.txt), enclosed in curly brackets like an alias, like: +# "{Right}" or "{Enter}". In # order to avoid naming conflicts between aliases +# and keycodes, the convention is to start aliases with a lowercase letter. # # Anything enclosed within curly brackets can also have a number after it, # indicating how many times that alias or keycode should be repeated. For From 647093f5c8b60c1b80d047b29b5db46f189dfa45 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 5 Nov 2020 16:01:44 -0800 Subject: [PATCH 098/112] quickfort guide: 'c' stockpiles don't make sense explain why "custom" stockpiles aren't meaningful to create in a blueprint and what the user should do instead --- docs/guides/quickfort-user-guide.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 964776f65..0df4bc24c 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -502,7 +502,12 @@ meeting area all at once: #zone main pasture and picnic area nmg(10x10) -The order of the individual letters doesn't matter. +The order of the individual letters doesn't matter. Note that "custom" +(:kbd:`c`) stockpiles cannot be declared in a blueprint since what would +get created would depend on what happens to be set in the "custom stockpile +settings" in your game. Instead, place stockpiles using the keys that +represent the types you want to store and then use a ``#query`` blueprint to +customize them. Detailed configuration for zones, such as the pit/pond toggle, can also be set by mimicking the hotkeys used to set them. Note that gather flags default to From 93c8de9b237996aa047f0741af7992a8c4eecc5f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 5 Nov 2020 16:11:27 -0800 Subject: [PATCH 099/112] use numbered lists in meta blueprints section --- docs/guides/quickfort-user-guide.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 0df4bc24c..f8061b766 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -897,13 +897,13 @@ Meta blueprints Meta blueprints are blueprints that script a series of other blueprints. Many blueprint packages follow this pattern: -- Apply dig blueprint to designate dig areas -- Wait for miners to dig -- **Apply build buildprint** to designate buildings -- **Apply place buildprint** to designate stockpiles -- **Apply query blueprint** to configure stockpiles -- Wait for buildings to get built -- Apply a different query blueprint to configure rooms +1. Apply dig blueprint to designate dig areas +#. Wait for miners to dig +#. **Apply build buildprint** to designate buildings +#. **Apply place buildprint** to designate stockpiles +#. **Apply query blueprint** to configure stockpiles +#. Wait for buildings to get built +#. Apply a different query blueprint to configure rooms Those three "apply"s in the middle might as well get done in one command instead of three. A meta blueprint can encode that sequence. A meta blueprint refers to @@ -947,12 +947,12 @@ blueprints into one: Now your sequence is shortened to: -- Apply dig blueprint to designate dig areas -- Wait for miners to dig -- **Apply meta buildprint** to build buildings and designate/configure - stockpiles -- Wait for buildings to get built -- Apply the final query blueprint to configure the room +1. Apply dig blueprint to designate dig areas +#. Wait for miners to dig +#. **Apply meta buildprint** to build buildings and designate/configure + stockpiles +#. Wait for buildings to get built +#. Apply the final query blueprint to configure the room You can use meta blueprints to lay out your fortress at a larger scale as well. The ``#<`` and ``#>`` notation is valid in meta blueprints, so you can, for From 76759184d3f54e6eb3bd9c94f3d3ef3835682afa Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 5 Nov 2020 16:18:33 -0800 Subject: [PATCH 100/112] reinforce that meta blueprints can't cross files they must refer to labels that are within the same .xlsx or .csv file --- docs/guides/quickfort-user-guide.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index f8061b766..5ab0956f7 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1015,6 +1015,11 @@ a big fort, so we're planning for a lot of bedrooms): Note that for blueprints without an explicit label, we still need to address them by their auto-generated numerical label. +It's worth calling out that ``#meta`` blueprints can only refer to blueprints +that are defined in the same file. This means that all blueprints that a +``#meta`` blueprint needs to script must be in sheets within the same +.xlsx spreadsheet or concatenated into the same .csv file. + You can then hide the blueprints that you now manage with the ``#meta``-mode blueprint from ``quickfort list`` by adding a ``hidden()`` marker to their modelines. That way the output of ``quickfort list`` won't be cluttered by From 01741ce6c607995ae9e4456394c2e334061072f3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 11 Nov 2020 16:53:29 -0800 Subject: [PATCH 101/112] update quickfort guide and aliases - move bit of alias documentation from aliases-common to aliases.txt - document new behavior for 'c'ustom stockpiles --- data/quickfort/aliases-common.txt | 7 +++---- dfhack-config/quickfort/aliases.txt | 6 +++++- docs/guides/quickfort-user-guide.rst | 11 +++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index 857160711..59705f9f7 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -17,10 +17,6 @@ # # Aliases that don't fit into those two categories have comments explaining # their usage. -# -# There is also a non-alphanumeric alias built into the code for the common -# shorthand for "make room": -# r+ expands to r+& ######################################## @@ -41,6 +37,9 @@ give10down: {give move={Down 10}} give10left: {give move={Left 10}} give10right: {give move={Right 10}} +# usage example: {givename name="myname"} +givename: {Ctrl}n{name}& + # use to toggle a sequence of stockpile options. for example: {togglesequence 5} togglesequence: &{Down} togglesequence2: &{Down 2} diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt index 2743a39f3..9be32107e 100644 --- a/dfhack-config/quickfort/aliases.txt +++ b/dfhack-config/quickfort/aliases.txt @@ -95,8 +95,12 @@ # ! expands to {Ctrl} # ^ expands to {ESC} # +# There is also a non-standard alias built into the code for the common +# shorthand for "make room": +# r+ expands to r+& +# # If you need literal verisons of the shorthand characters, surround them in -# curly brackets, for example: "{~}" +# curly brackets, for example: "{!}" # # # Add your custom aliases here: diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 5ab0956f7..1d9c1a024 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -502,12 +502,11 @@ meeting area all at once: #zone main pasture and picnic area nmg(10x10) -The order of the individual letters doesn't matter. Note that "custom" -(:kbd:`c`) stockpiles cannot be declared in a blueprint since what would -get created would depend on what happens to be set in the "custom stockpile -settings" in your game. Instead, place stockpiles using the keys that -represent the types you want to store and then use a ``#query`` blueprint to -customize them. +The order of the individual letters doesn't matter. If you want to configure the +stockpile from scratch in a ``#query`` blueprint, you can place unconfigured +"custom" stockpiles with (:kbd:`c`). It is more efficient, though, to place +stockpiles using the keys that represent the types you want to store, and +then only use a ``#query`` blueprint if you need fine-grained customization. Detailed configuration for zones, such as the pit/pond toggle, can also be set by mimicking the hotkeys used to set them. Note that gather flags default to From d2472335fce9fb431f5ea2dc3859776116176b76 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 12 Nov 2020 23:20:53 -0800 Subject: [PATCH 102/112] add names for quantums and zones --- data/quickfort/aliases-common.txt | 15 +++++++++++++-- dfhack-config/quickfort/aliases.txt | 12 ++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index 59705f9f7..e4d2ef1cd 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -37,7 +37,8 @@ give10down: {give move={Down 10}} give10left: {give move={Left 10}} give10right: {give move={Right 10}} -# usage example: {givename name="myname"} +# Keep in mind that building, stockpile, and zone names have a maximum length +# of 20 characters. usage example: {givename name="myname"} givename: {Ctrl}n{name}& # use to toggle a sequence of stockpile options. for example: {togglesequence 5} @@ -49,7 +50,9 @@ enablesequence: e{Down} # clothes and armor in this quantum stockpile will rot away. If you want bones # in your quantum stockpile, apply this alias to a refuse stockpile (but don't # put useful clothes or armor in there!) -quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{enablefurniture}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}{enableweapons}{enablearmor}{enablesheet} +# Optionally set a name for the stockpile by specifying the 'name' parameter, +# for example: {quantum name="my name"} +quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{enablefurniture}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}{enableweapons}{enablearmor}{enablesheet}{givename} ################################## @@ -84,6 +87,14 @@ quantumstopfromwest: {quantumstopprefix}s{Left}p^{Right}{quantumstopsuffix} quantumstopfromnorth: {quantumstopprefix}s{Up}p^{Down}{quantumstopsuffix} +################################## +# zone aliases +################################## + +# usage example: {namezone name="my zone name"} +namezone: ^i{givename}^q + + ################################## # animal stockpile adjustments ################################## diff --git a/dfhack-config/quickfort/aliases.txt b/dfhack-config/quickfort/aliases.txt index 9be32107e..1d1ebff9f 100644 --- a/dfhack-config/quickfort/aliases.txt +++ b/dfhack-config/quickfort/aliases.txt @@ -58,12 +58,12 @@ # The syntax for defining aliases is: # aliasname: keystrokes # -# Where aliasname is at least two letters or digits long and keystrokes are -# whatever you would type into the DF UI. A keystroke can also be a named -# keycode from the DF interface definition file (data/init/interface.txt), -# enclosed in curly brackets like an alias, like: "{Right}" or "{Enter}". In -# order to avoid naming conflicts between aliases and keycodes, the convention -# is to start aliases with a lowercase letter. +# Where aliasname is at least two letters or digits long (including dashes and +# underscores) and keystrokes are whatever you would type into the DF UI. A +# keystroke can also be a named keycode from the DF interface definition file +# (data/init/interface.txt), enclosed in curly brackets like an alias, like: +# "{Right}" or "{Enter}". In # order to avoid naming conflicts between aliases +# and keycodes, the convention is to start aliases with a lowercase letter. # # Anything enclosed within curly brackets can also have a number after it, # indicating how many times that alias or keycode should be repeated. For From efdba8b596a9696234bcf3e0677a4d76af936ef5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 13 Nov 2020 10:18:54 -0800 Subject: [PATCH 103/112] add buildingplan.isPlannedBuilding --- docs/Lua API.rst | 1 + plugins/buildingplan.cpp | 5 +++++ plugins/lua/buildingplan.lua | 1 + 3 files changed, 7 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 80fd0add6..ea91f9ca3 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3797,6 +3797,7 @@ Native functions provided by the `buildingplan` plugin: * ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan. * ``bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the buildingplan UI is enabled for the specified building type. +* ``bool isPlannedBuilding(df::building *bld)`` returns whether the given building is managed by buildingplan. * ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list. * ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. * ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index dedc89f62..aab308a3d 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1024,6 +1024,10 @@ static bool isPlannableBuilding(df::building_type type, toBuildingTypeKey(type, subtype, custom)); } +static bool isPlannedBuilding(df::building *bld) { + return !!planner.getPlannedBuilding(bld); +} + static void addPlannedBuilding(df::building *bld) { planner.addPlannedBuilding(bld); } @@ -1049,6 +1053,7 @@ static void setSetting(std::string name, bool value) { DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isPlanModeEnabled), DFHACK_LUA_FUNCTION(isPlannableBuilding), + DFHACK_LUA_FUNCTION(isPlannedBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding), DFHACK_LUA_FUNCTION(doCycle), DFHACK_LUA_FUNCTION(scheduleCycle), diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 9659b4867..f640969b8 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -7,6 +7,7 @@ local _ENV = mkmodule('plugins.buildingplan') * 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) * void doCycle() * void scheduleCycle() From 76e8c495fe3c487f1cc32e35b36b701f981953a8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 13 Nov 2020 10:45:53 -0800 Subject: [PATCH 104/112] revert skip_vector=true for now --- library/modules/Job.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 366384fd2..4feb33bad 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -605,7 +605,7 @@ bool Job::isSuitableItem(df::job_item *item, df::item_type itype, int isubtype) ItemTypeInfo iinfo(itype, isubtype); MaterialInfo minfo(item); - return iinfo.isValid() && iinfo.matches(*item, &minfo, true, itype); + return iinfo.isValid() && iinfo.matches(*item, &minfo, false, itype); } bool Job::isSuitableMaterial( @@ -619,7 +619,7 @@ bool Job::isSuitableMaterial( ItemTypeInfo iinfo(item); MaterialInfo minfo(mat_type, mat_index); - return minfo.isValid() && iinfo.matches(*item, &minfo, true, itype); + return minfo.isValid() && iinfo.matches(*item, &minfo, false, itype); } std::string Job::getName(df::job *job) From 170f4982b22bc0fce1dc41949750c6272f970db5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 13 Nov 2020 14:05:48 -0500 Subject: [PATCH 105/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ccf1d035a..9779422a6 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ccf1d035a653c991b669fccc8293ecbab449548f +Subproject commit 9779422a6071110240400f8be8e31c54efb9555e From 59e76ae407acbfd273a9f45d4a16c8d80b0e5403 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 13 Nov 2020 12:43:46 -0800 Subject: [PATCH 106/112] detect buildingplan buildings in the resume plugin --- plugins/CMakeLists.txt | 2 +- plugins/resume.cpp | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9f5b2c679..822ab531b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -154,7 +154,7 @@ if(BUILD_SUPPORTED) add_subdirectory(remotefortressreader) dfhack_plugin(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) add_subdirectory(rendermax) - dfhack_plugin(resume resume.cpp) + dfhack_plugin(resume resume.cpp LINK_LIBRARIES lua) dfhack_plugin(reveal reveal.cpp) dfhack_plugin(search search.cpp) dfhack_plugin(seedwatch seedwatch.cpp) diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 5909c0956..b4398dd24 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -11,6 +11,7 @@ // DF data structure definition headers #include "DataDefs.h" +#include "LuaTools.h" #include "MiscUtils.h" #include "Types.h" #include "df/viewscreen_dwarfmodest.h" @@ -85,6 +86,25 @@ struct SuspendedBuilding } }; +static bool is_planned_building(df::building *bld) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic( + out, L, "plugins.buildingplan", "isPlannedBuilding")) + return false; + + Lua::Push(L, bld); + + if (!Lua::SafeCall(out, L, 1, 1)) + return false; + + return lua_toboolean(L, -1); +} + DFHACK_PLUGIN_IS_ENABLED(enabled); static bool buildings_scanned = false; static vector suspended_buildings, resumed_buildings; @@ -101,7 +121,7 @@ void scan_for_suspended_buildings() if (job) { SuspendedBuilding sb(bld); - sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE; + sb.is_planned = is_planned_building(bld); auto it = resumed_buildings.begin(); From 28d599ee36264a1e6d54ea00df77367d884770c4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 13 Nov 2020 23:37:08 -0500 Subject: [PATCH 107/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 9779422a6..acf50cc63 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9779422a6071110240400f8be8e31c54efb9555e +Subproject commit acf50cc63897078b96a207c021b4745c1fa8998f From 85dfb67004afdf9068687bfc3885e016e1262161 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 14 Nov 2020 15:16:25 -0500 Subject: [PATCH 108/112] Move #1704's changelog entry to the correct release --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index c972b586a..0a5909ae3 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: - `buildingplan`: artifacts are now successfully matched when max quality is set to ``artifacts`` - `buildingplan`: no longer erroneously matches items to buildings while the game is paused - `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences +- `embark-assistant`: fixed an issue causing incursion resource matching (e.g. sand/clay) to skip some tiles if those resources were provided only through incursions ## Misc Improvements - `buildingplan`: all buildings, furniture, and constructions are now supported (except for instruments) @@ -64,7 +65,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `search`: fixed an issue causing item counts on the trade screen to display inconsistently when searching - `stockpiles`: fixed a crash when loading food stockpiles - `stockpiles`: fixed an error when saving furniture stockpiles -- `embark-assistant`: Fixed issue causing incursion resource matching to skip the checks if those resources were provided only through incursions. ## Misc Improvements - `createitem`: added support for plant growths (fruit, berries, leaves, etc.) From 14eeeeedc04203637b6d6e7290f31b6435d34542 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 15 Nov 2020 17:39:31 -0500 Subject: [PATCH 109/112] Remove extra paragraph from generated changelogs, restore paragraph margins The CSS (changed in bca76b8f) was removing space between actual paragraphs in lists. This was intended to address excess padding in changelogs, but that is resolved here by removing blank lines surrounding nested lists. This still displays properly on GitHub/Reddit and presumably other Markdown implementations as well. --- docs/sphinx_extensions/dfhack/changelog.py | 2 -- docs/styles/dfhack.css | 5 ----- 2 files changed, 7 deletions(-) diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index d38f8c060..37fe289cb 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -201,10 +201,8 @@ def print_changelog(versions, all_entries, path, replace=True, prefix=''): continue elif entry.children: write('- ' + entry.feature + ':') - write('') for child in entry.children: write(' - ' + child) - write('') else: write('- ' + entry.feature) write('') diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 7b735ed42..9b6e523ef 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -57,11 +57,6 @@ div.body { min-width: unset; } -div.body li > p { - margin-top: 0; - margin-bottom: 0; -} - span.pre { overflow-wrap: break-word; } From 8319d71dffd7048dbd9540eb35f9f01bf8ca1181 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 15 Nov 2020 18:42:14 -0500 Subject: [PATCH 110/112] Improve documentation of reqscript, dfhack_flags, etc. --- docs/Lua API.rst | 143 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 116 insertions(+), 27 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 0a253df9d..ffce7fd4e 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4338,48 +4338,125 @@ Arguments are passed in to the scripts via the ``...`` built-in quasi-variable; when the script is called by the DFHack core, they are all guaranteed to be non-nil strings. +Additional data about how a script is invoked is passed to the script as a +special ``dfhack_flags`` global, which is unique to each script. This table +is guaranteed to exist, but individual entries may be present or absent +depending on how the script was invoked. Flags that are present are described +in the subsections below. + DFHack invokes the scripts in the `core context `; however it is possible to call them from any lua code (including from other scripts) in any -context, via the same function the core uses: +context with ``dfhack.run_script()`` below. + +General script API +================== * ``dfhack.run_script(name[,args...])`` - Run a lua script in hack/scripts/, as if it was started from dfhack command-line. - The ``name`` argument should be the name stem, as would be used on the command line. + Run a Lua script in hack/scripts/, as if it was started from the DFHack + command-line. The ``name`` argument should be the name of the script without + its extension, as would be used on the command line. -Note that this function lets errors propagate to the caller. + Note that this function lets Lua errors propagate to the caller. -* ``dfhack.script_environment(name)`` + To run other types of commands (such as built-in commands, plugin commands, or + Ruby scripts), see ``dfhack.run_command()``. Note that this is slightly slower + than ``dfhack.run_script()`` for Lua scripts. - Run an Lua script and return its environment. - This command allows you to use scripts like modules for increased portability. - It is highly recommended that if you are a modder you put your custom modules in ``raw/scripts`` and use ``script_environment`` instead of ``require`` so that saves with your mod installed will be self-contained and can be transferred to people who do have DFHack but do not have your mod installed. +* ``dfhack.script_help([name, [extension]])`` - You can say ``dfhack.script_environment('add-thought').addEmotionToUnit([arguments go here])`` and it will have the desired effect. - It will call the script in question with the global ``moduleMode`` set to ``true`` so that the script can return early. - This is useful because if the script is called from the console it should deal with its console arguments and if it is called by ``script_environment`` it should only create its global functions and return. - You can also access global variables with, for example ``print(dfhack.script_environment('add-thought').validArgs)`` + Returns the contents of the embedded documentation of the specified script. + ``extension`` defaults to "lua", and ``name`` defaults to the name of the + script where this function was called. For example, the following can be used + to print the current script's help text:: + + local args = {...} + if args[1] == 'help' then + print(script_help()) + return + end - The function ``script_environment`` is fast enough that it is recommended that you not store its result in a nonlocal variable, because your script might need to load a different version of that script if the save is unloaded and a save with a different mod that overrides the same script with a slightly different functionality is loaded. - This will not be an issue in most cases. - This function also permits circular dependencies of scripts. +Importing scripts +================= * ``dfhack.reqscript(name)`` or ``reqscript(name)`` - Nearly identical to script_environment() but requires scripts being loaded to - include a line similar to:: + Loads a Lua script and returns its environment (i.e. a table of all global + functions and variables). This is similar to the built-in ``require()``, but + searches all script paths for the first matching ``name.lua`` file instead + of searching the Lua library paths (like ``hack/lua``). + + Most scripts can be made to support ``reqscript()`` without significant + changes (in contrast, ``require()`` requires the use of ``mkmodule()`` and + some additional boilerplate). However, because scripts can have side effects + when they are loaded (such as printing messages or modifying the game state), + scripts that intend to support being imported must satisfy some criteria to + ensure that they can be imported safely: + + 1. Include the following line - ``reqscript()`` will fail if this line is + not present:: --@ module = true - This is intended to only allow scripts that take appropriate action when used - as a module to be loaded. + 2. Include a check for ``dfhack_flags.module``, and avoid running any code + that has side-effects if this flag is true. For instance:: -* ``dfhack.script_help([name, [extension]])`` + -- (function definitions) + if dfhack_flags.module then + return + end + -- (main script code with side-effects) - Returns the contents of the embedded documentation of the specified script. - ``extension`` defaults to "lua", and ``name`` defaults to the name of the - script where this function was called. + or:: + + -- (function definitions) + function main() + -- (main script code with side-effects) + end + if not dfhack_flags.module then + main() + end + + Example usage:: + + local addThought = reqscript('add-thought') + addThought.addEmotionToUnit(unit, ...) + + Circular dependencies between scripts are supported, as long as the scripts + have no side-effects at load time (which should already be the case per + the above criteria). + + .. warning:: + + Avoid caching the table returned by ``reqscript()`` beyond storing it in + a local or global variable as in the example above. ``reqscript()`` is fast + for scripts that have previously been loaded and haven't changed. If you + retain a reference to a table returned by an old ``reqscript()`` call, this + may lead to unintended behavior if the location of the script changes + (e.g. if a save is loaded or unloaded, or if a `script path ` + is added in some other way). + + .. admonition:: Tip + + Mods that include custom Lua modules can write these modules to support + ``reqscript()`` and distribute them as scripts in ``raw/scripts``. Since the + entire ``raw`` folder is copied into new saves, this will allow saves to be + successfully transferred to other users who do not have the mod installed + (as long as they have DFHack installed). + + .. admonition:: Backwards compatibility notes + + For backwards compatibility, ``moduleMode`` is also defined if + ``dfhack_flags.module`` is defined, and is set to the same value. + Support for this may be removed in a future version. + +* ``dfhack.script_environment(name)`` + + Similar to ``reqscript()`` but does not enforce the check for module support. + This can be used to import scripts that support being used as a module but do + not declare support as described above, although it is preferred to update + such scripts so that ``reqscript()`` can be used instead. Enabling and disabling scripts ============================== @@ -4389,11 +4466,23 @@ by including the following line anywhere in their file:: --@ enable = true -When the ``enable`` and ``disable`` commands are invoked, a ``dfhack_flags`` -table will be passed to the script with the following fields set: +When the ``enable`` and ``disable`` commands are invoked, the ``dfhack_flags`` +table passed to the script will have the following fields set: -* ``enable``: Always true if the script is being enabled *or* disabled -* ``enable_state``: True if the script is being enabled, false otherwise +* ``enable``: Always ``true`` if the script is being enabled *or* disabled +* ``enable_state``: ``true`` if the script is being enabled, ``false`` otherwise + +Example usage:: + + -- @enable = true + -- (function definitions...) + if dfhack_flags.enable then + if dfhack_flags.enable_state then + start() + else + stop() + end + end Save init script ================ From 0b886399fe71402ef3ff95a00774f38e6da60f02 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 15 Nov 2020 22:18:54 -0500 Subject: [PATCH 111/112] zone: stop enumnick from taking priority over assign/unassign/slaughter Fixes #1709 Ref #1652 --- docs/changelog.txt | 1 + plugins/zone.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 0a5909ae3..8ca5fa316 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -39,6 +39,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `buildingplan`: no longer erroneously matches items to buildings while the game is paused - `dwarfmonitor`: fixed a crash when opening the ``prefs`` screen if units have vague preferences - `embark-assistant`: fixed an issue causing incursion resource matching (e.g. sand/clay) to skip some tiles if those resources were provided only through incursions +- `zone`: fixed an issue causing the ``enumnick`` subcommand to run when attempting to run ``assign``, ``unassign``, or ``slaughter`` ## Misc Improvements - `buildingplan`: all buildings, furniture, and constructions are now supported (except for instruments) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c32122241..25b4be950 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -449,7 +449,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", milkable"; if(unit->flags2.bits.slaughter) out << ", slaughter"; - + if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; @@ -1493,7 +1493,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool cagezone_assign = false; bool nick_set = false; string target_nick; - bool enum_nick = true; + bool enum_nick = false; string enum_prefix; bool verbose = false; @@ -1552,8 +1552,7 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } else { out.color(COLOR_BLUE); - out << "Current building unset (no building under" - "cursor)." << endl; + out << "Current building unset (no building under cursor)." << endl; out.reset_color(); return CR_OK; @@ -2127,7 +2126,7 @@ command_result df_zone (color_ostream &out, vector & parameters) } continue; } - + matchedCount++; if(unit_info) From ddbd22fcf4d33740945885c88d44f39c9ef92b86 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 15 Nov 2020 22:22:18 -0500 Subject: [PATCH 112/112] Update scripts --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index acf50cc63..5aaa1ce81 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit acf50cc63897078b96a207c021b4745c1fa8998f +Subproject commit 5aaa1ce815b2192c6b6a22523a996ed556c04fbd