From 82013c0c5ea145ef709048e0d130f9a184ffbd48 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 16 Oct 2020 13:52:23 -0700 Subject: [PATCH] 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); }