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); }