// Auto Material Select #include #include #include #include #include "Core.h" #include #include #include #include // DF data structure definition headers #include "DataDefs.h" #include "MiscUtils.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/enabler.h" #include "df/building_design.h" #include "modules/Gui.h" #include "modules/Screen.h" #include "modules/Buildings.h" #include "modules/Maps.h" #include "modules/Items.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" using std::map; using std::string; using std::vector; using namespace DFHack; using namespace df::enums; using df::global::gps; using df::global::ui; using df::global::ui_build_selector; using df::global::world; using df::global::enabler; DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.2 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; } }; static command_result automaterial_cmd(color_ostream &out, vector & parameters) { return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0) { Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); if (newline) { ++y; x = left_margin; } else x += text.length(); } void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t color = COLOR_WHITE) { OutputString(10, x, y, hotkey); string display(": "); display.append(text); OutputString(color, x, y, display, newline, left_margin); } void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE) { OutputHotkeyString(x, y, text, hotkey); OutputString(COLOR_WHITE, x, y, ": "); if (state) OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin); else OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin); } struct coord32_t { int32_t x, y, z; df::coord get_coord16() const { return df::coord(x, y, z); } }; typedef int8_t UIColor; const int ascii_to_enum_offset = interface_key::STRING_A048 - '0'; inline string int_to_string(const int n) { return static_cast( &(ostringstream() << n) )->str(); } static void set_to_limit(int &value, const int maximum, const int min = 0) { if (value < min) value = min; else if (value > maximum) value = maximum; } inline void paint_text(const UIColor color, const int &x, const int &y, const std::string &text, const UIColor background = 0) { Screen::paintString(Screen::Pen(' ', color, background), x, y, text); } static string pad_string(string text, const int size, const bool front = true, const bool trim = false) { if (text.length() > size) { if (trim && size > 10) { text = text.substr(0, size-3); text.append("..."); } return text; } string aligned(size - text.length(), ' '); if (front) { aligned.append(text); return aligned; } else { text.append(aligned); return text; } } #define MAX_MASK 10 #define MAX_MATERIAL 21 #define SIDEBAR_WIDTH 30 #define COLOR_TITLE COLOR_BLUE #define COLOR_UNSELECTED COLOR_GREY #define COLOR_SELECTED COLOR_WHITE #define COLOR_HIGHLIGHTED COLOR_GREEN /* * List classes */ template class ListEntry { public: T elem; string text; bool selected; ListEntry(string text, T elem) { this->text = text; this->elem = elem; selected = false; } }; template class ListColumn { public: string title; int highlighted_index; int display_start_offset; int32_t bottom_margin, search_margin, left_margin; bool search_entry_mode; bool multiselect; bool allow_null; bool auto_select; bool force_sort; ListColumn() { clear(); left_margin = 2; bottom_margin = 3; search_margin = 38; highlighted_index = 0; multiselect = false; allow_null = true; auto_select = false; search_entry_mode = false; force_sort = false; } void clear() { list.clear(); display_list.clear(); display_start_offset = 0; max_item_width = 0; resize(); } void resize() { display_max_rows = gps->dimy - 4 - bottom_margin; } void add(ListEntry &entry) { list.push_back(entry); if (entry.text.length() > max_item_width) max_item_width = entry.text.length(); } void add(const string &text, T &elem) { list.push_back(ListEntry(text, elem)); if (text.length() > max_item_width) max_item_width = text.length(); } virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {} void display(const bool is_selected_column) const { int32_t y = 2; paint_text(COLOR_TITLE, left_margin, y, title); int last_index_able_to_display = display_start_offset + display_max_rows; for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) { ++y; UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : COLOR_UNSELECTED; UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK; paint_text(fg_color, left_margin, y, display_list[i]->text, bg_color); int x = left_margin + display_list[i]->text.length() + 1; display_extras(display_list[i]->elem, x, y); } if (is_selected_column) { y = gps->dimy - bottom_margin; int32_t x = search_margin; OutputHotkeyString(x, y, "Search" ,"S"); if (!search_string.empty() || search_entry_mode) { OutputString(COLOR_WHITE, x, y, ": "); OutputString(COLOR_WHITE, x, y, search_string); if (search_entry_mode) OutputString(COLOR_LIGHTGREEN, x, y, "_"); } } } void filter_display() { ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; display_list.clear(); for (size_t i = 0; i < list.size(); i++) { if (search_string.empty() || list[i].text.find(search_string) != string::npos) { ListEntry *entry = &list[i]; display_list.push_back(entry); if (entry == prev_selected) highlighted_index = display_list.size() - 1; } } changeHighlight(0); } void selectDefaultEntry() { for (size_t i = 0; i < display_list.size(); i++) { if (display_list[i]->selected) { highlighted_index = i; break; } } } void validateHighlight() { set_to_limit(highlighted_index, display_list.size() - 1); if (highlighted_index < display_start_offset) display_start_offset = highlighted_index; else if (highlighted_index >= display_start_offset + display_max_rows) display_start_offset = highlighted_index - display_max_rows + 1; if (auto_select || (!allow_null && list.size() == 1)) display_list[highlighted_index]->selected = true; } void changeHighlight(const int highlight_change, const int offset_shift = 0) { if (!initHighlightChange()) return; highlighted_index += highlight_change + offset_shift * display_max_rows; display_start_offset += offset_shift * display_max_rows; set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows)); validateHighlight(); } void setHighlight(const int index) { if (!initHighlightChange()) return; highlighted_index = index; validateHighlight(); } bool initHighlightChange() { if (display_list.size() == 0) return false; if (auto_select && !multiselect) { for (typename vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) { it->selected = false; } } return true; } void toggleHighlighted() { if (auto_select) return; ListEntry *entry = display_list[highlighted_index]; if (!multiselect || !allow_null) { int selected_count = 0; for (size_t i = 0; i < list.size(); i++) { if (!multiselect && !entry->selected) list[i].selected = false; if (!allow_null && list[i].selected) selected_count++; } if (!allow_null && entry->selected && selected_count == 1) return; } entry->selected = !entry->selected; } vector getSelectedElems(bool only_one = false) { vector results; for (typename vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) { if ((*it).selected) { results.push_back(&(*it).elem); if (only_one) break; } } return results; } T* getFirstSelectedElem() { vector results = getSelectedElems(true); if (results.size() == 0) return NULL; else return results[0]; } size_t getDisplayListSize() { return display_list.size(); } size_t getBaseListSize() { return list.size(); } bool feed(set *input) { if (input->count(interface_key::CURSOR_UP)) { search_entry_mode = false; changeHighlight(-1); } else if (input->count(interface_key::CURSOR_DOWN)) { search_entry_mode = false; changeHighlight(1); } else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) { search_entry_mode = false; changeHighlight(0, -1); } else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) { search_entry_mode = false; changeHighlight(0, 1); } else if (search_entry_mode) { // Search query typing mode df::interface_key last_token = *input->rbegin(); if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) { // Standard character search_string += last_token - ascii_to_enum_offset; filter_display(); } else if (last_token == interface_key::STRING_A000) { // Backspace if (search_string.length() > 0) { search_string.erase(search_string.length()-1); filter_display(); } } else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) { // ENTER or ESC: leave typing mode search_entry_mode = false; } else if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) { // Arrow key pressed. Leave entry mode and allow screen to process key search_entry_mode = false; return false; } return true; } // Not in search query typing mode else if (input->count(interface_key::SELECT) && !auto_select) { toggleHighlighted(); } else if (input->count(interface_key::CUSTOM_S)) { search_entry_mode = true; } else if (input->count(interface_key::CUSTOM_SHIFT_S)) { search_string.clear(); filter_display(); } else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) { return setHighlightByMouse(); } else return false; return true; } bool setHighlightByMouse() { if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 && gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width) { int new_index = display_start_offset + gps->mouse_y - 3; if (new_index < display_list.size()) setHighlight(new_index); enabler->mouse_lbut = enabler->mouse_rbut = 0; return true; } return false; } static bool compareText(ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; } void doSort(bool (*function)(ListEntry const&, ListEntry const&)) { if (force_sort || list.size() < 100) std::sort(list.begin(), list.end(), function); filter_display(); } virtual void sort() { doSort(&compareText); } private: vector< ListEntry > list; vector< ListEntry* > display_list; string search_string; int display_max_rows; int max_item_width; }; /* * Material Choice Screen */ class ViewscreenChooseMaterial : public dfhack_viewscreen { public: static bool reset_list; ViewscreenChooseMaterial(); void feed(set *input); void render(); std::string getFocusString() { return "buildingplan_choosemat"; } private: ListColumn masks_column; ListColumn materials_column; vector< ListEntry > all_masks; int selected_column; df::building_type btype; void populateMasks(const bool set_defaults = false); void populateMaterials(const bool set_defaults = false); bool addMaterialEntry(df::dfhack_material_category &selected_category, MaterialInfo &material, string name, const bool set_defaults); virtual void resize(int32_t x, int32_t y); void validateColumn(); }; bool ViewscreenChooseMaterial::reset_list = false; ViewscreenChooseMaterial::ViewscreenChooseMaterial() { selected_column = 0; masks_column.title = "Type"; masks_column.multiselect = true; masks_column.left_margin = 2; materials_column.left_margin = MAX_MASK + 3; materials_column.title = "Material"; masks_column.changeHighlight(0); vector raw_masks; df::dfhack_material_category full_mat_mask, curr_mat_mask; full_mat_mask.whole = -1; curr_mat_mask.whole = 1; bitfield_to_string(&raw_masks, full_mat_mask); for (int i = 0; i < raw_masks.size(); i++) { if (raw_masks[i][0] == '?') break; all_masks.push_back(ListEntry(pad_string(raw_masks[i], MAX_MASK, false), curr_mat_mask)); curr_mat_mask.whole <<= 1; } populateMasks(); populateMaterials(); masks_column.selectDefaultEntry(); materials_column.selectDefaultEntry(); materials_column.changeHighlight(0); } void ViewscreenChooseMaterial::populateMasks(const bool set_defaults /*= false */) { masks_column.clear(); for (vector< ListEntry >::iterator it = all_masks.begin(); it != all_masks.end(); it++) { auto entry = *it; if (set_defaults) { //TODO //if (cv->mat_mask.whole & entry.elem.whole) //entry.selected = true; } masks_column.add(entry); } masks_column.sort(); } void ViewscreenChooseMaterial::populateMaterials(const bool set_defaults /*= false */) { materials_column.clear(); df::dfhack_material_category selected_category; vector selected_materials = masks_column.getSelectedElems(); if (selected_materials.size() == 1) selected_category = *selected_materials[0]; else if (selected_materials.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(), set_defaults); } } 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(), set_defaults); } materials_column.sort(); } bool ViewscreenChooseMaterial::addMaterialEntry(df::dfhack_material_category &selected_category, MaterialInfo &material, string name, const bool set_defaults) { bool selected = false; if (!selected_category.whole || material.matches(selected_category)) { ListEntry entry(pad_string(name, MAX_MATERIAL, false), material); if (set_defaults) { /*if (cv->material.matches(material)) { entry.selected = true; selected = true; }*/ //TODO } materials_column.add(entry); } return selected; } void ViewscreenChooseMaterial::feed(set *input) { bool key_processed; switch (selected_column) { case 0: key_processed = masks_column.feed(input); if (input->count(interface_key::SELECT)) populateMaterials(false); 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; } else if (input->count(interface_key::SEC_SELECT)) { df::dfhack_material_category mat_mask; vector selected_masks = masks_column.getSelectedElems(); for (vector::iterator it = selected_masks.begin(); it != selected_masks.end(); it++) { mat_mask.whole |= (*it)->whole; } MaterialInfo *selected_material = materials_column.getFirstSelectedElem(); MaterialInfo material = (selected_material) ? *selected_material : MaterialInfo(); //TODO reset_list = true; 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, "Save", "Shift-Enter"); x += 3; OutputHotkeyString(x, y, "Cancel", "Esc"); } void ViewscreenChooseMaterial::validateColumn() { set_to_limit(selected_column, 1); } void ViewscreenChooseMaterial::resize(int32_t x, int32_t y) { dfhack_viewscreen::resize(x, y); masks_column.resize(); materials_column.resize(); } // START Planning class PlannedBuilding { public: PlannedBuilding(df::building *building) : min_quality(item_quality::Ordinary) { this->building = building; pos = df::coord(building->centerx, building->centery, building->z); } df::building_type getType() { return building->getType(); } bool assignClosestItem(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 pos = (*item_iter)->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)) { items_vector->erase(closest_item); return true; } return false; } bool 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; auto job = building->jobs[0]; delete job->job_items[0]; 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 isValid() { return building && Buildings::findAtTile(pos) == building; } bool isCurrentlySelectedBuilding() { return building == world->selected_building; } private: df::building *building; PersistentDataItem config; df::coord pos; vector materials; df::dfhack_material_category mat_mask; item_quality::item_quality min_quality; }; class Planner { public: bool isPlanableBuilding(const df::building_type type) const { return item_for_building_type.find(type) != item_for_building_type.end(); } void reset() { planned_buildings.clear(); for(auto iter = world->buildings.all.begin(); iter != world->buildings.all.end(); iter++) { auto bld = *iter; if (isPlanableBuilding(bld->getType())) { if (bld->jobs.size() != 1) continue; auto job = bld->jobs[0]; if (!job->flags.bits.suspend) continue; if (job->job_items.size() != 1) continue; if (job->job_items[0]->item_type != item_type::NONE) continue; addPlannedBuilding(bld); } } } void initialize() { vector item_names; typedef df::enum_traits item_types; int size = item_types::last_item_value - item_types::first_item_value+1; for (size_t i = 1; i < size; i++) { is_relevant_item_type[(df::item_type) (i-1)] = false; string item_name = toLower(item_types::key_table[i]); string item_name_clean; for (auto c = item_name.begin(); c != item_name.end(); c++) { if (*c == '_') continue; item_name_clean += *c; } item_names.push_back(item_name_clean); } typedef df::enum_traits building_types; size = building_types::last_item_value - building_types::first_item_value+1; for (size_t i = 1; i < size; i++) { auto building_type = (df::building_type) (i-1); if (building_type == building_type::Weapon || building_type == building_type::Floodgate) continue; string building_name = toLower(building_types::key_table[i]); for (size_t j = 0; j < item_names.size(); j++) { if (building_name == item_names[j]) { item_for_building_type[(df::building_type) (i-1)] = (df::item_type) j; available_item_vectors[(df::item_type) j] = vector(); is_relevant_item_type[(df::item_type) j] = true; } } } } void addPlannedBuilding(df::building *bld) { PlannedBuilding pb(bld); planned_buildings.push_back(pb); } void doCycle() { if (planned_buildings.size() == 0) return; gather_available_items(); for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) { if (building_iter->isValid()) { 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)) { ++building_iter; continue; } } building_iter = planned_buildings.erase(building_iter); } } bool allocatePlannedBuilding(df::building_type type) { coord32_t cursor; if (!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; } for (auto iter = newinst->jobs.begin(); iter != newinst->jobs.end(); iter++) { (*iter)->flags.bits.suspend = true; } addPlannedBuilding(newinst); return true; } bool canUnsuspendSelectedBuilding() { for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) { if (building_iter->isValid() && building_iter->isCurrentlySelectedBuilding()) { return false; } } return true; } private: map item_for_building_type; map> available_item_vectors; map is_relevant_item_type; //Needed for fast check when loopin over all items vector planned_buildings; void 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(artifact1); #undef F std::vector &items = world->items.other[items_other_id::ANY_FREE]; 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.in_job || item->isAssignedToStockpile() || item->flags.bits.owned || item->flags.bits.in_chest) { continue; } available_item_vectors[itype].push_back(item); } } }; static Planner planner; static map planmode_enabled; static bool is_planmode_enabled(df::building_type type) { if (planmode_enabled.find(type) == planmode_enabled.end()) { planmode_enabled[type] = false; } return planmode_enabled[type]; } #define DAY_TICKS 1200 DFhackCExport command_result plugin_onupdate(color_ostream &out) { static decltype(world->frame_counter) last_frame_count = 0; if ((world->frame_counter - last_frame_count) >= DAY_TICKS/2) { last_frame_count = world->frame_counter; planner.doCycle(); } return CR_OK; } //START Viewscreen Hook struct buildingplan_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.canUnsuspendSelectedBuilding(); } bool isInPlannedBuildingPlacementMode() { return ui->main.mode == ui_sidebar_mode::Build && ui_build_selector && ui_build_selector->stage < 2 && planner.isPlanableBuilding(ui_build_selector->building_type); } bool handle_input(set *input) { if (isInPlannedBuildingPlacementMode()) { auto type = ui_build_selector->building_type; if (input->count(interface_key::CUSTOM_P)) { planmode_enabled[type] = !planmode_enabled[type]; if (!planmode_enabled[type]) { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); } return true; } if (is_planmode_enabled(type)) { if (input->count(interface_key::SELECT)) { if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type)) { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); } return true; } } } else if (isInPlannedBuildingQueryMode() && input->count(interface_key::SUSPENDBUILDING)) { return true; // Don't unsuspend planned buildings } return false; } DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { if (!handle_input(input)) INTERPOSE_NEXT(feed)(input); } DEFINE_VMETHOD_INTERPOSE(void, render, ()) { bool plannable = isInPlannedBuildingPlacementMode(); if (plannable && is_planmode_enabled(ui_build_selector->building_type)) { 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();) { //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)(); auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; if (plannable) { int y = 23; OutputToggleString(x, y, "Planning Mode", "p", is_planmode_enabled(ui_build_selector->building_type), true, left_margin); if (is_planmode_enabled(ui_build_selector->building_type)) { //OutputHotkeyString(x, y, "Material Filter", "m", true, left_margin); } } else if (isInPlannedBuildingQueryMode()) { // Hide suspend toggle option int y = 20; Screen::Pen pen(' ', COLOR_BLACK); Screen::fillRect(pen, x, y, dims.menu_x2, y); } } }; 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()) { out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl; } return CR_OK; } DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { if (!gps || !INTERPOSE_HOOK(buildingplan_hook, feed).apply() || !INTERPOSE_HOOK(buildingplan_hook, render).apply()) out.printerr("Could not insert buildingplan hooks!\n"); commands.push_back( PluginCommand( "buildingplan", "Place furniture before it's built", buildingplan_cmd, false, "")); planner.initialize(); return CR_OK; } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { case SC_MAP_LOADED: planner.reset(); break; default: break; } return CR_OK; }