From f3da131db7a8ec41389342eaf44bfe85a4f281d2 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 8 Sep 2020 00:17:56 -0700 Subject: [PATCH] reorganize buildingplan code - no logic changes - no API or logic changes, just moving code around - split buildingplan-lib into planner and rooms files - move business logic from .h files to .cpp files --- plugins/CMakeLists.txt | 2 +- plugins/buildingplan-lib.cpp | 688 +------------------------------ plugins/buildingplan-lib.h | 507 +---------------------- plugins/buildingplan-planner.cpp | 552 +++++++++++++++++++++++++ plugins/buildingplan-planner.h | 108 +++++ plugins/buildingplan-rooms.cpp | 227 ++++++++++ plugins/buildingplan-rooms.h | 51 +++ plugins/buildingplan.cpp | 311 +++++++++++++- plugins/fortplan.cpp | 20 +- 9 files changed, 1257 insertions(+), 1209 deletions(-) create mode 100644 plugins/buildingplan-planner.cpp create mode 100644 plugins/buildingplan-planner.h create mode 100644 plugins/buildingplan-rooms.cpp create mode 100644 plugins/buildingplan-rooms.h diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 07e14fa11..8301d05db 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -74,7 +74,7 @@ add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_TMP_FILES}) set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) -add_library(buildingplan-lib STATIC buildingplan-lib.cpp) +add_library(buildingplan-lib STATIC buildingplan-lib.cpp buildingplan-planner.cpp buildingplan-rooms.cpp) target_link_libraries(buildingplan-lib dfhack) # xlsxreader deps diff --git a/plugins/buildingplan-lib.cpp b/plugins/buildingplan-lib.cpp index e21c843aa..215dbb900 100644 --- a/plugins/buildingplan-lib.cpp +++ b/plugins/buildingplan-lib.cpp @@ -1,690 +1,16 @@ #include "buildingplan-lib.h" -#define PLUGIN_VERSION 0.00 -void debug(const string &msg) -{ - if (!show_debugging) - return; - - color_ostream_proxy out(Core::getInstance().getConsole()); - out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl; -} - -void enable_quickfort_fn(pair& pair) { pair.second = true; } - -/* - * Material Choice Screen - */ - -std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } - -bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) -{ - return (mat_mask.whole) ? mat.matches(mat_mask) : true; -} - -bool ItemFilter::matches(const df::dfhack_material_category mask) const -{ - return mask.whole & mat_mask.whole; -} - -bool ItemFilter::matches(DFHack::MaterialInfo &material) const -{ - for (auto it = materials.begin(); it != materials.end(); ++it) - if (material.matches(*it)) - return true; - return false; -} - -bool ItemFilter::matches(df::item *item) -{ - if (item->getQuality() < min_quality || item->getQuality() > max_quality) - return false; - - if (decorated_only && !item->hasImprovements()) - 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); -} - -std::vector ItemFilter::getMaterialFilterAsVector() -{ - 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"); +#include "Core.h" - return descriptions; -} - -std::string ItemFilter::getMaterialFilterAsSerial() -{ - std::string str; - - str.append(bitfield_to_string(mat_mask, ",")); - str.append("/"); - if (materials.size() > 0) - { - for (size_t i = 0; i < materials.size(); i++) - str.append(materials[i].getToken() + ","); - - if (str[str.size()-1] == ',') - str.resize(str.size () - 1); - } - - return str; -} - -bool ItemFilter::parseSerializedMaterialTokens(std::string str) -{ - valid = false; - std::vector tokens; - split_string(&tokens, str, "/"); +using namespace DFHack; - if (tokens.size() > 0 && !tokens[0].empty()) - { - if (!parseJobMaterialCategory(&mat_mask, tokens[0])) - return false; - } - - 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; - - materials.push_back(material); - } - } - - valid = true; - return true; -} - -std::string ItemFilter::getMinQuality() -{ - return ENUM_KEY_STR(item_quality, min_quality); -} - -std::string ItemFilter::getMaxQuality() -{ - return ENUM_KEY_STR(item_quality, max_quality); -} - -bool ItemFilter::isValid() -{ - return valid; -} - -void ItemFilter::clear() -{ - mat_mask.whole = 0; - materials.clear(); -} - -DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } - -ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) -{ - selected_column = 0; - masks_column.setTitle("Type"); - masks_column.multiselect = true; - masks_column.allow_search = false; - masks_column.left_margin = 2; - materials_column.left_margin = MAX_MASK + 3; - materials_column.setTitle("Material"); - materials_column.multiselect = true; - this->filter = filter; - - masks_column.changeHighlight(0); - - populateMasks(); - populateMaterials(); - - masks_column.selectDefaultEntry(); - materials_column.selectDefaultEntry(); - materials_column.changeHighlight(0); -} - -void ViewscreenChooseMaterial::feed(set *input) -{ - bool key_processed = false; - switch (selected_column) - { - case 0: - key_processed = masks_column.feed(input); - if (input->count(interface_key::SELECT)) - populateMaterials(); // Redo materials lists based on category selection - break; - case 1: - key_processed = materials_column.feed(input); - break; - } - - if (key_processed) - return; - - if (input->count(interface_key::LEAVESCREEN)) - { - input->clear(); - Screen::dismiss(this); - return; - } - if (input->count(interface_key::CUSTOM_SHIFT_C)) - { - filter->clear(); - masks_column.clearSelection(); - materials_column.clearSelection(); - populateMaterials(); - } - else if (input->count(interface_key::SEC_SELECT)) - { - // Convert list selections to material filters - - - filter->mat_mask.whole = 0; - filter->materials.clear(); - - // Category masks - auto masks = masks_column.getSelectedElems(); - for (auto it = masks.begin(); it != masks.end(); ++it) - filter->mat_mask.whole |= it->whole; - - // Specific materials - auto materials = materials_column.getSelectedElems(); - transform_(materials, filter->materials, material_info_identity_fn); - - Screen::dismiss(this); - } - else if (input->count(interface_key::CURSOR_LEFT)) - { - --selected_column; - validateColumn(); - } - else if (input->count(interface_key::CURSOR_RIGHT)) - { - selected_column++; - validateColumn(); - } - else if (enabler->tracking_on && enabler->mouse_lbut) - { - if (masks_column.setHighlightByMouse()) - selected_column = 0; - else if (materials_column.setHighlightByMouse()) - selected_column = 1; - - enabler->mouse_lbut = enabler->mouse_rbut = 0; - } -} - -void ViewscreenChooseMaterial::render() -{ - if (Screen::isDismissed(this)) - return; - - dfhack_viewscreen::render(); - - Screen::clear(); - Screen::drawBorder(" Building Material "); - - masks_column.display(selected_column == 0); - materials_column.display(selected_column == 1); - - int32_t y = gps->dimy - 3; - int32_t x = 2; - OutputHotkeyString(x, y, "Toggle", interface_key::SELECT); - x += 3; - OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT); - x += 3; - OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C); - x += 3; - OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN); -} - -// START Room Reservation -ReservedRoom::ReservedRoom(df::building *building, std::string noble_code) -{ - this->building = building; - config = DFHack::World::AddPersistentData("buildingplan/reservedroom"); - config.val() = noble_code; - config.ival(1) = building->id; - pos = df::coord(building->centerx, building->centery, building->z); -} - -ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &out) -{ - this->config = config; - - building = df::building::find(config.ival(1)); - if (!building) - return; - pos = df::coord(building->centerx, building->centery, building->z); -} - -bool ReservedRoom::checkRoomAssignment() -{ - if (!isValid()) - return false; - - auto np = getOwnersNobleCode(); - bool correctOwner = false; - for (auto iter = np.begin(); iter != np.end(); iter++) - { - if (iter->position->code == getCode()) - { - correctOwner = true; - break; - } - } - - if (correctOwner) - return true; - - for (auto iter = world->units.active.begin(); iter != world->units.active.end(); iter++) - { - df::unit* unit = *iter; - if (!Units::isCitizen(unit)) - continue; - - if (!Units::isActive(unit)) - continue; - - np = getUniqueNoblePositions(unit); - for (auto iter = np.begin(); iter != np.end(); iter++) - { - if (iter->position->code == getCode()) - { - Buildings::setOwner(building, unit); - break; - } - } - } - - return true; -} - -std::string RoomMonitor::getReservedNobleCode(int32_t buildingId) -{ - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) - { - if (buildingId == iter->getId()) - return iter->getCode(); - } - - return ""; -} - -void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code) -{ - bool found = false; - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) - { - if (buildingId != iter->getId()) - { - continue; - } - else - { - if (noble_code == iter->getCode()) - { - iter->remove(); - reservedRooms.erase(iter); - } - else - { - iter->setCode(noble_code); - } - found = true; - break; - } - } - - if (!found) - { - ReservedRoom room(df::building::find(buildingId), noble_code); - reservedRooms.push_back(room); - } -} - -void RoomMonitor::doCycle() -{ - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();) - { - if (iter->checkRoomAssignment()) - { - ++iter; - } - else - { - iter->remove(); - iter = reservedRooms.erase(iter); - } - } -} - -void RoomMonitor::reset(color_ostream &out) -{ - reservedRooms.clear(); - std::vector items; - DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom"); - - for (auto i = items.begin(); i != items.end(); i++) - { - ReservedRoom rr(*i, out); - if (rr.isValid()) - addRoom(rr); - } -} - - -void delete_item_fn(df::job_item *x) { delete x; } - -// START Planning - -PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) -{ - 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; -} - -PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out) -{ - this->config = config; - - if (!filter.parseSerializedMaterialTokens(config.val())) - { - out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); - return; - } - - building = df::building::find(config.ival(1)); - if (!building) - return; - - 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; -} - -bool PlannedBuilding::assignClosestItem(std::vector *items_vector) -{ - decltype(items_vector->begin()) closest_item; - int32_t closest_distance = -1; - for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) - { - auto item = *item_iter; - if (!filter.matches(item)) - continue; - - auto pos = item->pos; - auto distance = abs(pos.x - building->centerx) + - abs(pos.y - building->centery) + - abs(pos.z - building->z) * 50; - - if (closest_distance > -1 && distance >= closest_distance) - continue; - - closest_distance = distance; - closest_item = item_iter; - } - - if (closest_distance > -1 && assignItem(*closest_item)) - { - debug("Item assigned"); - items_vector->erase(closest_item); - remove(); - return true; - } - - return false; -} - -bool PlannedBuilding::assignItem(df::item *item) -{ - auto ref = df::allocate(); - if (!ref) - { - Core::printerr("Could not allocate general_ref_building_holderst\n"); - return false; - } - - ref->building_id = building->id; - - if (building->jobs.size() != 1) - return false; - - auto job = building->jobs[0]; - - for_each_(job->job_items, delete_item_fn); - job->job_items.clear(); - job->flags.bits.suspend = false; - - bool rough = false; - Job::attachJobItem(job, item, df::job_item_ref::Hauled); - if (item->getType() == item_type::BOULDER) - rough = true; - building->mat_type = item->getMaterial(); - building->mat_index = item->getMaterialIndex(); - - job->mat_type = building->mat_type; - job->mat_index = building->mat_index; - - if (building->needsDesign()) - { - auto act = (df::building_actual *) building; - act->design = new df::building_design(); - act->design->flags.bits.rough = rough; - } - - return true; -} - -bool PlannedBuilding::isValid() -{ - bool valid = filter.isValid() && - building && Buildings::findAtTile(pos) == building && - building->getBuildStage() == 0; - - if (!valid) - remove(); - - return valid; -} - -void Planner::reset(color_ostream &out) -{ - planned_buildings.clear(); - std::vector items; - DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); - - for (auto i = items.begin(); i != items.end(); i++) - { - PlannedBuilding pb(*i, out); - if (pb.isValid()) - planned_buildings.push_back(pb); - } -} - -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; \ - if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \ - planmode_enabled[df::building_type::btype] = false - - FOR_ENUM_ITEMS(item_type, it) - is_relevant_item_type[it] = false; - - add_building_type(Armorstand, ARMORSTAND); - add_building_type(Bed, BED); - add_building_type(Chair, CHAIR); - add_building_type(Coffin, COFFIN); - add_building_type(Door, DOOR); - add_building_type(Floodgate, FLOODGATE); - add_building_type(Hatch, HATCH_COVER); - add_building_type(GrateWall, GRATE); - add_building_type(GrateFloor, GRATE); - add_building_type(BarsVertical, BAR); - add_building_type(BarsFloor, BAR); - add_building_type(Cabinet, CABINET); - add_building_type(Box, BOX); - // skip kennels, farm plot - add_building_type(Weaponrack, WEAPONRACK); - add_building_type(Statue, STATUE); - add_building_type(Slab, SLAB); - add_building_type(Table, TABLE); - // skip roads ... furnaces - add_building_type(WindowGlass, WINDOW); - // skip gem window ... support - add_building_type(AnimalTrap, ANIMALTRAP); - add_building_type(Chain, CHAIN); - add_building_type(Cage, CAGE); - // skip archery target - add_building_type(TractionBench, TRACTION_BENCH); - // skip nest box, hive (tools) - -#undef add_building_type -} +bool show_debugging = false; -void Planner::doCycle() +void debug(const std::string &msg) { - debug("Running Cycle"); - if (planned_buildings.size() == 0) + if (!show_debugging) return; - debug("Planned count: " + int_to_string(planned_buildings.size())); - - gather_available_items(); - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) - { - if (building_iter->isValid()) - { - if (show_debugging) - debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); - - 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); - } -} - -bool Planner::allocatePlannedBuilding(df::building_type type) -{ - coord32_t cursor; - if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) - return false; - - auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); - if (!newinst) - return false; - - df::job_item *filter = new df::job_item(); - filter->item_type = item_type::NONE; - filter->mat_index = 0; - filter->flags2.bits.building_material = true; - std::vector filters; - filters.push_back(filter); - - if (!Buildings::constructWithFilters(newinst, filters)) - { - delete newinst; - return false; - } - - if (type == building_type::Door) - { - auto door = virtual_cast(newinst); - if (door) - door->door_flags.bits.pet_passable = true; - } - - addPlannedBuilding(newinst); - - return true; -} - -PlannedBuilding *Planner::getSelectedPlannedBuilding() -{ - for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) - { - if (building_iter->isCurrentlySelectedBuilding()) - { - return &(*building_iter); - } - } - - return nullptr; -} - -void Planner::adjustMinQuality(df::building_type type, int amount) -{ - 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; - -} - -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; -} - -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; + color_ostream_proxy out(Core::getInstance().getConsole()); + out << "DEBUG: " << msg << endl; } - -map planmode_enabled, saved_planmodes; - -bool show_debugging = false; -bool show_help = false; - -Planner planner; - -RoomMonitor roomMonitor; diff --git a/plugins/buildingplan-lib.h b/plugins/buildingplan-lib.h index d8d276a55..61a00e796 100644 --- a/plugins/buildingplan-lib.h +++ b/plugins/buildingplan-lib.h @@ -1,509 +1,8 @@ -#ifndef BUILDINGPLAN_H -#define BUILDINGPLAN_H +#pragma once -#include "uicommon.h" -#include "listcolumn.h" - -#include - -// DF data structure definition headers -#include "DataDefs.h" -#include "Types.h" -#include "df/build_req_choice_genst.h" -#include "df/build_req_choice_specst.h" -#include "df/item.h" -#include "df/ui.h" -#include "df/ui_build_selector.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/items_other_id.h" -#include "df/job.h" -#include "df/world.h" -#include "df/building_constructionst.h" -#include "df/building_design.h" -#include "df/entity_position.h" - -#include "modules/Buildings.h" -#include "modules/Maps.h" -#include "modules/Items.h" -#include "modules/Units.h" -#include "modules/Gui.h" - -#include "TileTypes.h" -#include "df/job_item.h" -#include "df/dfhack_material_category.h" -#include "df/general_ref_building_holderst.h" -#include "modules/Job.h" -#include "df/building_design.h" -#include "df/buildings_other_id.h" -#include "modules/World.h" -#include "df/building.h" -#include "df/building_doorst.h" - -using df::global::ui; -using df::global::ui_build_selector; -using df::global::world; - -struct MaterialDescriptor -{ - df::item_type item_type; - int16_t item_subtype; - int16_t type; - int32_t index; - bool valid; - - bool matches(const MaterialDescriptor &a) const - { - return a.valid && valid && - a.type == type && - a.index == index && - a.item_type == item_type && - a.item_subtype == item_subtype; - } -}; - -#define MAX_MASK 10 -#define MAX_MATERIAL 21 -#define SIDEBAR_WIDTH 30 - -static inline bool canReserveRoom(df::building *building) -{ - if (!building) - return false; - - if (building->jobs.size() > 0 && building->jobs[0]->job_type == job_type::DestroyBuilding) - return false; - - return building->is_room; -} - -static inline std::vector getUniqueNoblePositions(df::unit *unit) -{ - std::vector np; - Units::getNoblePositions(&np, unit); - for (auto iter = np.begin(); iter != np.end(); iter++) - { - if (iter->position->code == "MILITIA_CAPTAIN") - { - np.erase(iter); - break; - } - } - - return np; -} - -void delete_item_fn(df::job_item *x); - -MaterialInfo &material_info_identity_fn(MaterialInfo &m); - -extern map planmode_enabled, saved_planmodes; - -void enable_quickfort_fn(pair& pair); +#include "buildingplan-planner.h" +#include "buildingplan-rooms.h" void debug(const std::string &msg); -std::string material_to_string_fn(MaterialInfo m); extern bool show_debugging; -extern bool show_help; - -struct ItemFilter -{ - df::dfhack_material_category mat_mask; - std::vector materials; - df::item_quality min_quality; - df::item_quality max_quality; - - bool decorated_only; - - 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) - } - - 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; -}; - -class ViewscreenChooseMaterial : public dfhack_viewscreen -{ -public: - ViewscreenChooseMaterial(ItemFilter *filter); - - void feed(set *input); - - void render(); - - std::string getFocusString() { return "buildingplan_choosemat"; } - -private: - ListColumn masks_column; - ListColumn materials_column; - int selected_column; - ItemFilter *filter; - - df::building_type btype; - - 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)) - entry.selected = true; - - masks_column.add(entry); - } - - void populateMasks() - { - masks_column.clear(); - df::dfhack_material_category mask; - - mask.whole = 0; - mask.bits.stone = true; - addMaskEntry(mask, "Stone"); - - mask.whole = 0; - mask.bits.wood = true; - addMaskEntry(mask, "Wood"); - - mask.whole = 0; - mask.bits.metal = true; - addMaskEntry(mask, "Metal"); - - mask.whole = 0; - mask.bits.soap = true; - addMaskEntry(mask, "Soap"); - - masks_column.filterDisplay(); - } - - void populateMaterials() - { - materials_column.clear(); - df::dfhack_material_category selected_category; - std::vector selected_masks = masks_column.getSelectedElems(); - if (selected_masks.size() == 1) - selected_category = selected_masks[0]; - else if (selected_masks.size() > 1) - return; - - df::world_raws &raws = world->raws; - for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++) - { - auto obj = raws.mat_table.builtin[i]; - if (obj) - { - MaterialInfo material; - material.decode(i, -1); - addMaterialEntry(selected_category, material, material.toString()); - } - } - - for (size_t i = 0; i < raws.inorganics.size(); i++) - { - df::inorganic_raw *p = raws.inorganics[i]; - MaterialInfo material; - material.decode(0, i); - addMaterialEntry(selected_category, material, material.toString()); - } - - decltype(selected_category) wood_flag; - wood_flag.bits.wood = true; - if (!selected_category.whole || selected_category.bits.wood) - { - for (size_t i = 0; i < raws.plants.all.size(); i++) - { - df::plant_raw *p = raws.plants.all[i]; - for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++) - { - auto t = p->material[j]; - if (p->material[j]->id != "WOOD") - continue; - - MaterialInfo material; - 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)) - entry.selected = true; - - materials_column.add(entry); - } - } - } - materials_column.sort(); - } - - void addMaterialEntry(df::dfhack_material_category &selected_category, - MaterialInfo &material, std::string name) - { - if (!selected_category.whole || material.matches(selected_category)) - { - ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); - if (filter->matches(material)) - entry.selected = true; - - materials_column.add(entry); - } - } - - void validateColumn() - { - set_to_limit(selected_column, 1); - } - - void resize(int32_t x, int32_t y) - { - dfhack_viewscreen::resize(x, y); - masks_column.resize(); - materials_column.resize(); - } -}; - -class ReservedRoom -{ -public: - ReservedRoom(df::building *building, std::string noble_code); - - ReservedRoom(PersistentDataItem &config, color_ostream &out); - - bool checkRoomAssignment(); - - void remove() { DFHack::World::DeletePersistentData(config); } - - bool isValid() - { - if (!building) - return false; - - if (Buildings::findAtTile(pos) != building) - return false; - - return canReserveRoom(building); - } - - int32_t getId() - { - if (!isValid()) - return 0; - - return building->id; - } - - std::string getCode() { return config.val(); } - - void setCode(const std::string &noble_code) { config.val() = noble_code; } - -private: - df::building *building; - PersistentDataItem config; - df::coord pos; - - std::vector getOwnersNobleCode() - { - if (!building->owner) - return std::vector (); - - return getUniqueNoblePositions(building->owner); - } -}; - -class RoomMonitor -{ -public: - RoomMonitor() { } - - std::string getReservedNobleCode(int32_t buildingId); - - void toggleRoomForPosition(int32_t buildingId, std::string noble_code); - - void doCycle(); - - void reset(color_ostream &out); - -private: - std::vector reservedRooms; - - void addRoom(ReservedRoom &rr) - { - for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) - { - if (iter->getId() == rr.getId()) - return; - } - - reservedRooms.push_back(rr); - } -}; - -// START Planning -class PlannedBuilding -{ -public: - PlannedBuilding(df::building *building, ItemFilter *filter); - - PlannedBuilding(PersistentDataItem &config, color_ostream &out); - - bool assignClosestItem(std::vector *items_vector); - - bool assignItem(df::item *item); - - bool isValid(); - - df::building_type getType() { return building->getType(); } - - bool isCurrentlySelectedBuilding() { return isValid() && (building == world->selected_building); } - - ItemFilter *getFilter() { return &filter; } - - void remove() { DFHack::World::DeletePersistentData(config); } - -private: - df::building *building; - PersistentDataItem config; - df::coord pos; - ItemFilter filter; -}; - -class Planner -{ -public: - bool in_dummmy_screen; - - Planner() : in_dummmy_screen(false), quickfort_mode(false) { } - - bool isPlanableBuilding(const df::building_type type) const - { - return item_for_building_type.find(type) != item_for_building_type.end(); - } - - void reset(color_ostream &out); - - void initialize(); - - void addPlannedBuilding(df::building *bld) - { - for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++) - { - (*iter)->flags.bits.suspend = true; - } - - PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); - planned_buildings.push_back(pb); - } - - void doCycle(); - - bool allocatePlannedBuilding(df::building_type type); - - PlannedBuilding *getSelectedPlannedBuilding(); - - void removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); } - - ItemFilter *getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; } - - void adjustMinQuality(df::building_type type, int amount); - void adjustMaxQuality(df::building_type type, int amount); - - void enableQuickfortMode() - { - saved_planmodes = planmode_enabled; - for_each_(planmode_enabled, enable_quickfort_fn); - - quickfort_mode = true; - } - - void disableQuickfortMode() - { - planmode_enabled = saved_planmodes; - quickfort_mode = false; - } - - bool inQuickFortMode() { return quickfort_mode; } - -private: - map item_for_building_type; - map default_item_filters; - map> available_item_vectors; - map is_relevant_item_type; //Needed for fast check when looping over all items - bool quickfort_mode; - - std::vector planned_buildings; - - void boundsCheckItemQuality(item_quality::item_quality *quality); - - void gather_available_items() - { - debug("Gather available items"); - for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) - { - iter->second.clear(); - } - - // Precompute a bitmask with the bad flags - df::item_flags bad_flags; - bad_flags.whole = 0; - -#define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); -#undef F - - std::vector &items = world->items.other[items_other_id::IN_PLAY]; - - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - - if (item->flags.whole & bad_flags.whole) - continue; - - df::item_type itype = item->getType(); - if (!is_relevant_item_type[itype]) - continue; - - if (itype == item_type::BOX && item->isBag()) - continue; //Skip bags - - if (item->flags.bits.artifact) - continue; - - if (item->flags.bits.in_job || - item->isAssignedToStockpile() || - item->flags.bits.owned || - item->flags.bits.in_chest) - { - continue; - } - - available_item_vectors[itype].push_back(item); - } - } -}; - -extern Planner planner; - -extern RoomMonitor roomMonitor; - -#endif diff --git a/plugins/buildingplan-planner.cpp b/plugins/buildingplan-planner.cpp new file mode 100644 index 000000000..31caf995e --- /dev/null +++ b/plugins/buildingplan-planner.cpp @@ -0,0 +1,552 @@ +#include "df/general_ref_building_holderst.h" +#include "df/job_item.h" +#include "df/building_doorst.h" +#include "df/building_design.h" + +#include "modules/Job.h" +#include "modules/Buildings.h" +#include "modules/Gui.h" + +#include "buildingplan-planner.h" +#include "buildingplan-lib.h" +#include "uicommon.h" + +/* +* ItemFilter +*/ + +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) +} + +bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) +{ + return (mat_mask.whole) ? mat.matches(mat_mask) : true; +} + +bool ItemFilter::matches(const df::dfhack_material_category mask) const +{ + return mask.whole & mat_mask.whole; +} + +bool ItemFilter::matches(DFHack::MaterialInfo &material) const +{ + for (auto it = materials.begin(); it != materials.end(); ++it) + if (material.matches(*it)) + return true; + return false; +} + +bool ItemFilter::matches(df::item *item) +{ + if (item->getQuality() < min_quality || item->getQuality() > max_quality) + return false; + + if (decorated_only && !item->hasImprovements()) + 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); +} + +std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); } + +std::vector ItemFilter::getMaterialFilterAsVector() +{ + 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::getMaterialFilterAsSerial() +{ + std::string str; + + str.append(bitfield_to_string(mat_mask, ",")); + str.append("/"); + if (materials.size() > 0) + { + for (size_t i = 0; i < materials.size(); i++) + str.append(materials[i].getToken() + ","); + + if (str[str.size()-1] == ',') + str.resize(str.size () - 1); + } + + return str; +} + +bool ItemFilter::parseSerializedMaterialTokens(std::string str) +{ + valid = false; + std::vector tokens; + split_string(&tokens, str, "/"); + + if (tokens.size() > 0 && !tokens[0].empty()) + { + if (!parseJobMaterialCategory(&mat_mask, tokens[0])) + return false; + } + + 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; + + materials.push_back(material); + } + } + + valid = true; + return true; +} + +std::string ItemFilter::getMinQuality() +{ + return ENUM_KEY_STR(item_quality, min_quality); +} + +std::string ItemFilter::getMaxQuality() +{ + return ENUM_KEY_STR(item_quality, max_quality); +} + +bool ItemFilter::isValid() +{ + return valid; +} + +void ItemFilter::clear() +{ + mat_mask.whole = 0; + materials.clear(); +} + +/* +* PlannedBuilding +*/ + +PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) +{ + 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; +} + +PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out) +{ + this->config = config; + + if (!filter.parseSerializedMaterialTokens(config.val())) + { + out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str()); + return; + } + + building = df::building::find(config.ival(1)); + if (!building) + return; + + 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; +} + +bool PlannedBuilding::assignClosestItem(std::vector *items_vector) +{ + decltype(items_vector->begin()) closest_item; + int32_t closest_distance = -1; + for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++) + { + auto item = *item_iter; + if (!filter.matches(item)) + continue; + + auto pos = item->pos; + auto distance = abs(pos.x - building->centerx) + + abs(pos.y - building->centery) + + abs(pos.z - building->z) * 50; + + if (closest_distance > -1 && distance >= closest_distance) + continue; + + closest_distance = distance; + closest_item = item_iter; + } + + if (closest_distance > -1 && assignItem(*closest_item)) + { + debug("Item assigned"); + items_vector->erase(closest_item); + remove(); + return true; + } + + return false; +} + +void delete_item_fn(df::job_item *x) { delete x; } + +bool PlannedBuilding::assignItem(df::item *item) +{ + auto ref = df::allocate(); + if (!ref) + { + Core::printerr("Could not allocate general_ref_building_holderst\n"); + return false; + } + + ref->building_id = building->id; + + if (building->jobs.size() != 1) + return false; + + auto job = building->jobs[0]; + + for_each_(job->job_items, delete_item_fn); + job->job_items.clear(); + job->flags.bits.suspend = false; + + bool rough = false; + Job::attachJobItem(job, item, df::job_item_ref::Hauled); + if (item->getType() == item_type::BOULDER) + rough = true; + building->mat_type = item->getMaterial(); + building->mat_index = item->getMaterialIndex(); + + job->mat_type = building->mat_type; + job->mat_index = building->mat_index; + + if (building->needsDesign()) + { + auto act = (df::building_actual *) building; + act->design = new df::building_design(); + act->design->flags.bits.rough = rough; + } + + return true; +} + +bool PlannedBuilding::isValid() +{ + bool valid = filter.isValid() && + building && Buildings::findAtTile(pos) == building && + building->getBuildStage() == 0; + + if (!valid) + remove(); + + return valid; +} + +df::building_type PlannedBuilding::getType() +{ + return building->getType(); +} + +bool PlannedBuilding::isCurrentlySelectedBuilding() +{ + return isValid() && (building == df::global::world->selected_building); +} + +ItemFilter *PlannedBuilding::getFilter() +{ + return &filter; +} + +void PlannedBuilding::remove() +{ + DFHack::World::DeletePersistentData(config); +} + +/* +* Planner +*/ + +Planner::Planner() : in_dummmy_screen(false), quickfort_mode(false) { } + +void enable_quickfort_fn(pair& pair) { pair.second = true; } + +bool Planner::isPlanableBuilding(const df::building_type type) const +{ + return item_for_building_type.find(type) != item_for_building_type.end(); +} + +void Planner::reset(color_ostream &out) +{ + planned_buildings.clear(); + std::vector items; + DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); + + for (auto i = items.begin(); i != items.end(); i++) + { + PlannedBuilding pb(*i, out); + if (pb.isValid()) + planned_buildings.push_back(pb); + } +} + +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; \ + if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \ + planmode_enabled[df::building_type::btype] = false + + FOR_ENUM_ITEMS(item_type, it) + is_relevant_item_type[it] = false; + + add_building_type(Armorstand, ARMORSTAND); + add_building_type(Bed, BED); + add_building_type(Chair, CHAIR); + add_building_type(Coffin, COFFIN); + add_building_type(Door, DOOR); + add_building_type(Floodgate, FLOODGATE); + add_building_type(Hatch, HATCH_COVER); + add_building_type(GrateWall, GRATE); + add_building_type(GrateFloor, GRATE); + add_building_type(BarsVertical, BAR); + add_building_type(BarsFloor, BAR); + add_building_type(Cabinet, CABINET); + add_building_type(Box, BOX); + // skip kennels, farm plot + add_building_type(Weaponrack, WEAPONRACK); + add_building_type(Statue, STATUE); + add_building_type(Slab, SLAB); + add_building_type(Table, TABLE); + // skip roads ... furnaces + add_building_type(WindowGlass, WINDOW); + // skip gem window ... support + add_building_type(AnimalTrap, ANIMALTRAP); + add_building_type(Chain, CHAIN); + add_building_type(Cage, CAGE); + // skip archery target + add_building_type(TractionBench, TRACTION_BENCH); + // skip nest box, hive (tools) + +#undef add_building_type +} + +void Planner::addPlannedBuilding(df::building *bld) +{ + for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++) + { + (*iter)->flags.bits.suspend = true; + } + + PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); + planned_buildings.push_back(pb); +} + +void Planner::doCycle() +{ + debug("Running Cycle"); + if (planned_buildings.size() == 0) + return; + + debug("Planned count: " + int_to_string(planned_buildings.size())); + + gather_available_items(); + for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) + { + if (building_iter->isValid()) + { + if (show_debugging) + debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); + + 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); + } +} + +bool Planner::allocatePlannedBuilding(df::building_type type) +{ + coord32_t cursor; + if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) + return false; + + auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); + if (!newinst) + return false; + + df::job_item *filter = new df::job_item(); + filter->item_type = item_type::NONE; + filter->mat_index = 0; + filter->flags2.bits.building_material = true; + std::vector filters; + filters.push_back(filter); + + if (!Buildings::constructWithFilters(newinst, filters)) + { + delete newinst; + return false; + } + + if (type == building_type::Door) + { + auto door = virtual_cast(newinst); + if (door) + door->door_flags.bits.pet_passable = true; + } + + addPlannedBuilding(newinst); + + return true; +} + +PlannedBuilding *Planner::getSelectedPlannedBuilding() +{ + for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) + { + if (building_iter->isCurrentlySelectedBuilding()) + { + return &(*building_iter); + } + } + + return nullptr; +} + +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) +{ + 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; + +} + +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; +} + +void Planner::enableQuickfortMode() +{ + saved_planmodes = planmode_enabled; + for_each_(planmode_enabled, enable_quickfort_fn); + + quickfort_mode = true; +} + +void Planner::disableQuickfortMode() +{ + planmode_enabled = saved_planmodes; + quickfort_mode = false; +} + +bool Planner::inQuickFortMode() { return quickfort_mode; } + +void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) +{ + *quality = static_cast(*quality); + if (*quality > item_quality::Artifact) + (*quality) = item_quality::Artifact; + if (*quality < item_quality::Ordinary) + (*quality) = item_quality::Ordinary; +} + +void Planner::gather_available_items() +{ + debug("Gather available items"); + for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) + { + iter->second.clear(); + } + + // Precompute a bitmask with the bad flags + df::item_flags bad_flags; + bad_flags.whole = 0; + + #define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); + #undef F + + std::vector &items = df::global::world->items.other[df::items_other_id::IN_PLAY]; + + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + + if (item->flags.whole & bad_flags.whole) + continue; + + df::item_type itype = item->getType(); + if (!is_relevant_item_type[itype]) + continue; + + if (itype == df::item_type::BOX && item->isBag()) + continue; //Skip bags + + if (item->flags.bits.artifact) + continue; + + if (item->flags.bits.in_job || + item->isAssignedToStockpile() || + item->flags.bits.owned || + item->flags.bits.in_chest) + { + continue; + } + + available_item_vectors[itype].push_back(item); + } +} + +std::map planmode_enabled, saved_planmodes; +Planner planner; diff --git a/plugins/buildingplan-planner.h b/plugins/buildingplan-planner.h new file mode 100644 index 000000000..b073e96b8 --- /dev/null +++ b/plugins/buildingplan-planner.h @@ -0,0 +1,108 @@ +#pragma once + +#include "df/item_quality.h" +#include "df/dfhack_material_category.h" +#include "modules/Materials.h" +#include "modules/Persistence.h" + +struct ItemFilter +{ + 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; +}; + +class PlannedBuilding +{ +public: + PlannedBuilding(df::building *building, ItemFilter *filter); + PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); + + bool assignClosestItem(std::vector *items_vector); + bool assignItem(df::item *item); + + bool isValid(); + void remove(); + + df::building_type getType(); + bool isCurrentlySelectedBuilding(); + + ItemFilter *getFilter(); + +private: + df::building *building; + DFHack::PersistentDataItem config; + df::coord pos; + ItemFilter filter; +}; + +class Planner +{ +public: + bool in_dummmy_screen; + + Planner(); + + bool isPlanableBuilding(const df::building_type type) const; + + void reset(DFHack::color_ostream &out); + + void initialize(); + + void addPlannedBuilding(df::building *bld); + + void doCycle(); + + bool allocatePlannedBuilding(df::building_type type); + + PlannedBuilding *getSelectedPlannedBuilding(); + + void removeSelectedPlannedBuilding(); + + ItemFilter *getDefaultItemFilterForType(df::building_type type); + + void adjustMinQuality(df::building_type type, int amount); + void adjustMaxQuality(df::building_type type, int amount); + + void enableQuickfortMode(); + void disableQuickfortMode(); + bool inQuickFortMode(); + +private: + std::map item_for_building_type; + std::map default_item_filters; + std::map> available_item_vectors; + std::map is_relevant_item_type; //Needed for fast check when looping over all items + bool quickfort_mode; + + std::vector planned_buildings; + + void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality); + + void gather_available_items(); +}; + +extern std::map planmode_enabled, saved_planmodes; +extern Planner planner; diff --git a/plugins/buildingplan-rooms.cpp b/plugins/buildingplan-rooms.cpp new file mode 100644 index 000000000..87cd413d8 --- /dev/null +++ b/plugins/buildingplan-rooms.cpp @@ -0,0 +1,227 @@ +#include "buildingplan-rooms.h" +#include "buildingplan-lib.h" + +#include +#include +#include + +#include +#include +#include + +using namespace DFHack; + +bool canReserveRoom(df::building *building) +{ + if (!building) + return false; + + if (building->jobs.size() > 0 && building->jobs[0]->job_type == df::job_type::DestroyBuilding) + return false; + + return building->is_room; +} + +std::vector getUniqueNoblePositions(df::unit *unit) +{ + std::vector np; + Units::getNoblePositions(&np, unit); + for (auto iter = np.begin(); iter != np.end(); iter++) + { + if (iter->position->code == "MILITIA_CAPTAIN") + { + np.erase(iter); + break; + } + } + + return np; +} + +/* + * ReservedRoom + */ + +ReservedRoom::ReservedRoom(df::building *building, std::string noble_code) +{ + this->building = building; + config = DFHack::World::AddPersistentData("buildingplan/reservedroom"); + config.val() = noble_code; + config.ival(1) = building->id; + pos = df::coord(building->centerx, building->centery, building->z); +} + +ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &) +{ + this->config = config; + + building = df::building::find(config.ival(1)); + if (!building) + return; + pos = df::coord(building->centerx, building->centery, building->z); +} + +bool ReservedRoom::checkRoomAssignment() +{ + if (!isValid()) + return false; + + auto np = getOwnersNobleCode(); + bool correctOwner = false; + for (auto iter = np.begin(); iter != np.end(); iter++) + { + if (iter->position->code == getCode()) + { + correctOwner = true; + break; + } + } + + if (correctOwner) + return true; + + for (auto iter = df::global::world->units.active.begin(); iter != df::global::world->units.active.end(); iter++) + { + df::unit* unit = *iter; + if (!Units::isCitizen(unit)) + continue; + + if (!Units::isActive(unit)) + continue; + + np = getUniqueNoblePositions(unit); + for (auto iter = np.begin(); iter != np.end(); iter++) + { + if (iter->position->code == getCode()) + { + Buildings::setOwner(building, unit); + break; + } + } + } + + return true; +} + +void ReservedRoom::remove() { DFHack::World::DeletePersistentData(config); } + +bool ReservedRoom::isValid() +{ + if (!building) + return false; + + if (Buildings::findAtTile(pos) != building) + return false; + + return canReserveRoom(building); +} + +int32_t ReservedRoom::getId() +{ + if (!isValid()) + return 0; + + return building->id; +} + +std::string ReservedRoom::getCode() { return config.val(); } + +void ReservedRoom::setCode(const std::string &noble_code) { config.val() = noble_code; } + +std::vector ReservedRoom::getOwnersNobleCode() +{ + if (!building->owner) + return std::vector (); + + return getUniqueNoblePositions(building->owner); +} + +/* + * RoomMonitor + */ + +std::string RoomMonitor::getReservedNobleCode(int32_t buildingId) +{ + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) + { + if (buildingId == iter->getId()) + return iter->getCode(); + } + + return ""; +} + +void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code) +{ + bool found = false; + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) + { + if (buildingId != iter->getId()) + { + continue; + } + else + { + if (noble_code == iter->getCode()) + { + iter->remove(); + reservedRooms.erase(iter); + } + else + { + iter->setCode(noble_code); + } + found = true; + break; + } + } + + if (!found) + { + ReservedRoom room(df::building::find(buildingId), noble_code); + reservedRooms.push_back(room); + } +} + +void RoomMonitor::doCycle() +{ + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();) + { + if (iter->checkRoomAssignment()) + { + ++iter; + } + else + { + iter->remove(); + iter = reservedRooms.erase(iter); + } + } +} + +void RoomMonitor::reset(color_ostream &out) +{ + reservedRooms.clear(); + std::vector items; + DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom"); + + for (auto i = items.begin(); i != items.end(); i++) + { + ReservedRoom rr(*i, out); + if (rr.isValid()) + addRoom(rr); + } +} + +void RoomMonitor::addRoom(ReservedRoom &rr) +{ + for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++) + { + if (iter->getId() == rr.getId()) + return; + } + + reservedRooms.push_back(rr); +} + +RoomMonitor roomMonitor; diff --git a/plugins/buildingplan-rooms.h b/plugins/buildingplan-rooms.h new file mode 100644 index 000000000..3880dbe06 --- /dev/null +++ b/plugins/buildingplan-rooms.h @@ -0,0 +1,51 @@ +#pragma once + +#include "modules/Persistence.h" +#include "modules/Units.h" + +class ReservedRoom +{ +public: + ReservedRoom(df::building *building, std::string noble_code); + + ReservedRoom(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); + + bool checkRoomAssignment(); + void remove(); + bool isValid(); + + int32_t getId(); + std::string getCode(); + void setCode(const std::string &noble_code); + +private: + df::building *building; + DFHack::PersistentDataItem config; + df::coord pos; + + std::vector getOwnersNobleCode(); +}; + +class RoomMonitor +{ +public: + RoomMonitor() { } + + std::string getReservedNobleCode(int32_t buildingId); + + void toggleRoomForPosition(int32_t buildingId, std::string noble_code); + + void doCycle(); + + void reset(DFHack::color_ostream &out); + +private: + std::vector reservedRooms; + + void addRoom(ReservedRoom &rr); +}; + +bool canReserveRoom(df::building *building); +std::vector getUniqueNoblePositions(df::unit *unit); + +extern RoomMonitor roomMonitor; diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index c42aeac53..ae8ce8c8d 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,41 +1,297 @@ -#include "LuaTools.h" #include "buildingplan-lib.h" + +#include "df/entity_position.h" +#include "df/interface_key.h" +#include "df/ui_build_selector.h" +#include "df/viewscreen_dwarfmodest.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/World.h" + +#include "LuaTools.h" +#include "PluginManager.h" + +#include "uicommon.h" +#include "listcolumn.h" DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.14 +#define PLUGIN_VERSION 0.15 REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(world); -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} +#define MAX_MASK 10 +#define MAX_MATERIAL 21 +using namespace DFHack; +using namespace df::enums; -static bool is_planmode_enabled(df::building_type type) +bool show_help = false; + +class ViewscreenChooseMaterial : public dfhack_viewscreen { - if (planmode_enabled.find(type) == planmode_enabled.end()) +public: + ViewscreenChooseMaterial(ItemFilter *filter); + + void feed(set *input); + + void render(); + + std::string getFocusString() { return "buildingplan_choosemat"; } + +private: + ListColumn masks_column; + ListColumn materials_column; + int selected_column; + ItemFilter *filter; + + df::building_type btype; + + void addMaskEntry(df::dfhack_material_category &mask, const std::string &text) { - return false; + auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); + if (filter->matches(mask)) + entry.selected = true; + + masks_column.add(entry); } - return planmode_enabled[type]; + void populateMasks() + { + masks_column.clear(); + df::dfhack_material_category mask; + + mask.whole = 0; + mask.bits.stone = true; + addMaskEntry(mask, "Stone"); + + mask.whole = 0; + mask.bits.wood = true; + addMaskEntry(mask, "Wood"); + + mask.whole = 0; + mask.bits.metal = true; + addMaskEntry(mask, "Metal"); + + mask.whole = 0; + mask.bits.soap = true; + addMaskEntry(mask, "Soap"); + + masks_column.filterDisplay(); + } + + void populateMaterials() + { + materials_column.clear(); + df::dfhack_material_category selected_category; + std::vector selected_masks = masks_column.getSelectedElems(); + if (selected_masks.size() == 1) + selected_category = selected_masks[0]; + else if (selected_masks.size() > 1) + return; + + df::world_raws &raws = world->raws; + for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++) + { + auto obj = raws.mat_table.builtin[i]; + if (obj) + { + MaterialInfo material; + material.decode(i, -1); + addMaterialEntry(selected_category, material, material.toString()); + } + } + + for (size_t i = 0; i < raws.inorganics.size(); i++) + { + df::inorganic_raw *p = raws.inorganics[i]; + MaterialInfo material; + material.decode(0, i); + addMaterialEntry(selected_category, material, material.toString()); + } + + decltype(selected_category) wood_flag; + wood_flag.bits.wood = true; + if (!selected_category.whole || selected_category.bits.wood) + { + for (size_t i = 0; i < raws.plants.all.size(); i++) + { + df::plant_raw *p = raws.plants.all[i]; + for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++) + { + auto t = p->material[j]; + if (p->material[j]->id != "WOOD") + continue; + + MaterialInfo material; + 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)) + entry.selected = true; + + materials_column.add(entry); + } + } + } + materials_column.sort(); + } + + void addMaterialEntry(df::dfhack_material_category &selected_category, + MaterialInfo &material, std::string name) + { + if (!selected_category.whole || material.matches(selected_category)) + { + ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); + if (filter->matches(material)) + entry.selected = true; + + materials_column.add(entry); + } + } + + void validateColumn() + { + set_to_limit(selected_column, 1); + } + + void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + masks_column.resize(); + materials_column.resize(); + } +}; + +DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; } + +ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter) +{ + selected_column = 0; + masks_column.setTitle("Type"); + masks_column.multiselect = true; + masks_column.allow_search = false; + masks_column.left_margin = 2; + materials_column.left_margin = MAX_MASK + 3; + materials_column.setTitle("Material"); + materials_column.multiselect = true; + this->filter = filter; + + masks_column.changeHighlight(0); + + populateMasks(); + populateMaterials(); + + masks_column.selectDefaultEntry(); + materials_column.selectDefaultEntry(); + materials_column.changeHighlight(0); } -#define DAY_TICKS 1200 -DFhackCExport command_result plugin_onupdate(color_ostream &out) +void ViewscreenChooseMaterial::feed(set *input) { - if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0) + bool key_processed = false; + switch (selected_column) { - planner.doCycle(); - roomMonitor.doCycle(); + case 0: + key_processed = masks_column.feed(input); + if (input->count(interface_key::SELECT)) + populateMaterials(); // Redo materials lists based on category selection + break; + case 1: + key_processed = materials_column.feed(input); + break; } - return CR_OK; + if (key_processed) + return; + + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + if (input->count(interface_key::CUSTOM_SHIFT_C)) + { + filter->clear(); + masks_column.clearSelection(); + materials_column.clearSelection(); + populateMaterials(); + } + else if (input->count(interface_key::SEC_SELECT)) + { + // Convert list selections to material filters + filter->mat_mask.whole = 0; + filter->materials.clear(); + + // Category masks + auto masks = masks_column.getSelectedElems(); + for (auto it = masks.begin(); it != masks.end(); ++it) + filter->mat_mask.whole |= it->whole; + + // Specific materials + auto materials = materials_column.getSelectedElems(); + transform_(materials, filter->materials, material_info_identity_fn); + + Screen::dismiss(this); + } + else if (input->count(interface_key::CURSOR_LEFT)) + { + --selected_column; + validateColumn(); + } + else if (input->count(interface_key::CURSOR_RIGHT)) + { + selected_column++; + validateColumn(); + } + else if (enabler->tracking_on && enabler->mouse_lbut) + { + if (masks_column.setHighlightByMouse()) + selected_column = 0; + else if (materials_column.setHighlightByMouse()) + selected_column = 1; + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + } +} + +void ViewscreenChooseMaterial::render() +{ + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Building Material "); + + masks_column.display(selected_column == 0); + materials_column.display(selected_column == 1); + + int32_t y = gps->dimy - 3; + int32_t x = 2; + OutputHotkeyString(x, y, "Toggle", interface_key::SELECT); + x += 3; + OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT); + x += 3; + OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C); + x += 3; + OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN); } //START Viewscreen Hook +static bool is_planmode_enabled(df::building_type type) +{ + if (planmode_enabled.find(type) == planmode_enabled.end()) + { + return false; + } + + return planmode_enabled[type]; +} + struct buildingplan_hook : public df::viewscreen_dwarfmodest { //START UI Methods @@ -58,8 +314,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest bool isInPlannedBuildingPlacementMode() { return ui->main.mode == ui_sidebar_mode::Build && - ui_build_selector && - ui_build_selector->stage < 2 && + df::global::ui_build_selector && + df::global::ui_build_selector->stage < 2 && planner.isPlanableBuilding(ui_build_selector->building_type); } @@ -350,7 +606,6 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); - static command_result buildingplan_cmd(color_ostream &out, vector & parameters) { if (!parameters.empty()) @@ -401,7 +656,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector frame_counter % (DAY_TICKS/2) == 0) + { + planner.doCycle(); + roomMonitor.doCycle(); + } + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &) +{ + return CR_OK; +} + // Lua API section static bool isPlannableBuilding(df::building_type type) { diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp index 5dceb35c9..ccaab18c3 100644 --- a/plugins/fortplan.cpp +++ b/plugins/fortplan.cpp @@ -1,11 +1,25 @@ -#include "buildingplan-lib.h" #include #include + +#include "df/world.h" +#include "df/trap_type.h" + #include "modules/Filesystem.h" +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/World.h" + +#include "PluginManager.h" + +#include "buildingplan-lib.h" +#include "uicommon.h" DFHACK_PLUGIN("fortplan"); #define PLUGIN_VERSION 0.15 +using namespace std; +using namespace DFHack; + command_result fortplan(color_ostream &out, vector & params); struct BuildingInfo { @@ -88,7 +102,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector frame_counter % (DAY_TICKS/2) == 0) + if (Maps::IsValid() && !World::ReadPauseState() && df::global::world->frame_counter % (DAY_TICKS/2) == 0) { planner.doCycle(); } @@ -100,7 +114,7 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!gps) + if (!df::global::gps ) return CR_FAILURE; if (enable != is_enabled)