From b0892214007a09cde86f1093bbc7b33c1e2d5807 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 25 Oct 2012 00:09:34 +1300 Subject: [PATCH 001/138] Inventory Monitor - first checkin --- plugins/workflow.cpp | 1416 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 1304 insertions(+), 112 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 98258682e..345dfa578 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -38,6 +38,31 @@ #include "df/inorganic_raw.h" #include "df/builtin_mats.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/itemdef_weaponst.h" +#include "df/itemdef_trapcompst.h" +#include "df/itemdef_toyst.h" +#include "df/itemdef_toolst.h" +#include "df/itemdef_instrumentst.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_ammost.h" +#include "df/itemdef_siegeammost.h" +#include "df/itemdef_glovesst.h" +#include "df/itemdef_shoesst.h" +#include "df/itemdef_shieldst.h" +#include "df/itemdef_helmst.h" +#include "df/itemdef_pantsst.h" +#include "df/itemdef_foodst.h" +#include "df/trapcomp_flags.h" + +#include +#include +#include +#include +#include "df/creature_raw.h" +using df::global::gps; +using std::deque; + using std::vector; using std::string; using std::endl; @@ -47,6 +72,7 @@ using namespace df::enums; using df::global::world; using df::global::ui; using df::global::ui_workshop_job_cursor; +using df::global::ui_workshop_in_add; using df::global::job_next_id; /* Plugin registration */ @@ -58,100 +84,28 @@ static void cleanup_state(color_ostream &out); DFHACK_PLUGIN("workflow"); -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - if (!world || !ui) - return CR_FAILURE; - if (ui_workshop_job_cursor && job_next_id) { - commands.push_back( - PluginCommand( - "workflow", "Manage control of repeat jobs.", - workflow_cmd, false, - " workflow enable [option...]\n" - " workflow disable [option...]\n" - " If no options are specified, enables or disables the plugin.\n" - " Otherwise, enables or disables any of the following options:\n" - " - drybuckets: Automatically empty abandoned water buckets.\n" - " - auto-melt: Resume melt jobs when there are objects to melt.\n" - " workflow jobs\n" - " List workflow-controlled jobs (if in a workshop, filtered by it).\n" - " workflow list\n" - " List active constraints, and their job counts.\n" - " workflow list-commands\n" - " List workflow commands that re-create existing constraints.\n" - " workflow count [cnt-gap]\n" - " workflow amount [cnt-gap]\n" - " Set a constraint. The first form counts each stack as only 1 item.\n" - " workflow unlimit \n" - " Delete a constraint.\n" - " workflow unlimit-all\n" - " Delete all constraints.\n" - "Function:\n" - " - When the plugin is enabled, it protects all repeat jobs from removal.\n" - " If they do disappear due to any cause, they are immediately re-added\n" - " to their workshop and suspended.\n" - " - In addition, when any constraints on item amounts are set, repeat jobs\n" - " that produce that kind of item are automatically suspended and resumed\n" - " as the item amount goes above or below the limit. The gap specifies how\n" - " much below the limit the amount has to drop before jobs are resumed;\n" - " this is intended to reduce the frequency of jobs being toggled.\n" - "Constraint examples:\n" - " workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100\n" - " workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50\n" - " Keep metal bolts within 900-1000, and wood/bone within 150-200.\n" - " workflow count FOOD 120 30\n" - " workflow count DRINK 120 30\n" - " Keep the number of prepared food & drink stacks between 90 and 120\n" - " workflow count BIN 30\n" - " workflow count BARREL 30\n" - " workflow count BOX/CLOTH,SILK,YARN 30\n" - " Make sure there are always 25-30 empty bins/barrels/bags.\n" - " workflow count BAR//COAL 20\n" - " workflow count BAR//COPPER 30\n" - " Make sure there are always 15-20 coal and 25-30 copper bars.\n" - " workflow count CRAFTS//GOLD 20\n" - " Produce 15-20 gold crafts.\n" - " workflow count POWDER_MISC/SAND 20\n" - " workflow count BOULDER/CLAY 20\n" - " Collect 15-20 sand bags and clay boulders.\n" - " workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20\n" - " Make sure there are always 80-100 units of dimple dye.\n" - " In order for this to work, you have to set the material of\n" - " the PLANT input on the Mill Plants job to MUSHROOM_CUP_DIMPLE\n" - " using the 'job item-material' command.\n" - ) - ); +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; } - - init_state(out); - - return CR_OK; + else + x += text.length(); } -DFhackCExport command_result plugin_shutdown (color_ostream &out) +void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0) { - cleanup_state(out); - - return CR_OK; + OutputString(10, x, y, hotkey); + string display(": "); + display.append(text); + OutputString(15, x, y, display, newline, left_margin); } -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_MAP_LOADED: - cleanup_state(out); - init_state(out); - break; - case SC_MAP_UNLOADED: - cleanup_state(out); - break; - default: - break; - } - return CR_OK; -} /****************************** * JOB STATE TRACKING STRUCTS * @@ -272,6 +226,8 @@ int ProtectedJob::cur_tick_idx = 0; typedef std::map, bool> TMaterialCache; +static int max_history_days = 14; + struct ItemConstraint { PersistentDataItem config; @@ -293,6 +249,8 @@ struct ItemConstraint { TMaterialCache material_cache; + deque history; + public: ItemConstraint() : is_craft(false), weight(0), min_quality(item_quality::Ordinary),item_amount(0), @@ -327,6 +285,14 @@ public: int size = goalByCount() ? item_count : item_amount; request_resume = (size <= goalCount()-goalGap()); request_suspend = (size >= goalCount()); + + if (max_history_days > 0) + { + history.push_back(size); + if (history.size() > max_history_days * 2) + history.pop_front(); + } + } }; @@ -547,6 +513,15 @@ static bool recover_job(color_ostream &out, ProtectedJob *pj) return true; } +static ProtectedJob *add_known_job(df::job *job) +{ + ProtectedJob *pj = new ProtectedJob(job); + assert(pj->holder); + known_jobs[pj->id] = pj; + + return pj; +} + static void check_lost_jobs(color_ostream &out, int ticks) { ProtectedJob::cur_tick_idx++; @@ -567,9 +542,7 @@ static void check_lost_jobs(color_ostream &out, int ticks) } else if (job->flags.bits.repeat && isSupportedJob(job)) { - pj = new ProtectedJob(job); - assert(pj->holder); - known_jobs[pj->id] = pj; + add_known_job(job); } } @@ -605,6 +578,23 @@ static void recover_jobs(color_ostream &out) static void process_constraints(color_ostream &out); +#define ITEMDEF_VECTORS \ + ITEM(WEAPON, weapons, itemdef_weaponst) \ + ITEM(TRAPCOMP, trapcomps, itemdef_trapcompst) \ + ITEM(TOY, toys, itemdef_toyst) \ + ITEM(TOOL, tools, itemdef_toolst) \ + ITEM(INSTRUMENT, instruments, itemdef_instrumentst) \ + ITEM(ARMOR, armor, itemdef_armorst) \ + ITEM(AMMO, ammo, itemdef_ammost) \ + ITEM(SIEGEAMMO, siege_ammo, itemdef_siegeammost) \ + ITEM(GLOVES, gloves, itemdef_glovesst) \ + ITEM(SHOES, shoes, itemdef_shoesst) \ + ITEM(SHIELD, shields, itemdef_shieldst) \ + ITEM(HELM, helms, itemdef_helmst) \ + ITEM(PANTS, pants, itemdef_pantsst) \ + ITEM(FOOD, food, itemdef_foodst) + +static bool first_update_done = false; DFhackCExport command_result plugin_onupdate(color_ostream &out) { if (!enabled) @@ -612,7 +602,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) // Every 5 frames check the jobs for disappearance static unsigned cnt = 0; - if ((++cnt % 5) != 0) + if ((++cnt % 5) != 0 && !first_update_done) return CR_OK; check_lost_jobs(out, world->frame_counter - last_tick_frame_count); @@ -622,28 +612,55 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) static unsigned last_rlen = 0; bool check_time = (world->frame_counter - last_frame_count) >= DAY_TICKS/2; - if (pending_recover.size() != last_rlen || check_time) + if (pending_recover.size() != last_rlen || check_time || !first_update_done) { recover_jobs(out); last_rlen = pending_recover.size(); // If the half-day passed, proceed to update - if (check_time) + if (check_time || !first_update_done) { last_frame_count = world->frame_counter; update_job_data(out); process_constraints(out); + first_update_done = true; } } + return CR_OK; } + /****************************** * ITEM COUNT CONSTRAINT * ******************************/ +static ItemConstraint * create_new_constraint(bool is_craft, ItemTypeInfo item, MaterialInfo material, + df::dfhack_material_category mat_mask, item_quality::item_quality minqual, + int weight, PersistentDataItem * cfg, const std::string & str) +{ + ItemConstraint *nct = new ItemConstraint; + nct->is_craft = is_craft; + nct->item = item; + nct->material = material; + nct->mat_mask = mat_mask; + nct->min_quality = minqual; + nct->weight = weight; + + if (cfg) + nct->config = *cfg; + else + { + nct->config = Core::getInstance().getWorld()->AddPersistentData("workflow/constraints"); + nct->init(str); + } + + constraints.push_back(nct); + return nct; +} + static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg) { std::vector tokens; @@ -717,23 +734,8 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str return ct; } - ItemConstraint *nct = new ItemConstraint; - nct->is_craft = is_craft; - nct->item = item; - nct->material = material; - nct->mat_mask = mat_mask; - nct->min_quality = minqual; - nct->weight = weight; - - if (cfg) - nct->config = *cfg; - else - { - nct->config = Core::getInstance().getWorld()->AddPersistentData("workflow/constraints"); - nct->init(str); - } + ItemConstraint *nct = create_new_constraint(is_craft, item, material, mat_mask, minqual, weight, cfg, str); - constraints.push_back(nct); return nct; } @@ -766,10 +768,11 @@ static bool isCraftItem(df::item_type type) static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t isubtype, df::dfhack_material_category mat_mask, int16_t mat_type, int32_t mat_index, - bool is_craft = false) + bool is_craft = false, bool create_constraint = false) { MaterialInfo mat(mat_type, mat_index); + bool constraint_found = false; for (size_t i = 0; i < constraints.size(); i++) { ItemConstraint *ct = constraints[i]; @@ -807,10 +810,30 @@ static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t i ct->jobs.push_back(pj); pj->constraints.push_back(ct); + constraint_found = true; if (!ct->is_active && pj->isResumed()) ct->is_active = true; } + + if (create_constraint && !constraint_found) + { + ItemTypeInfo item; + item.type = itype; + item.subtype = isubtype; + + int weight = 0; + if (item.subtype >= 0) + weight += 10000; + if (mat_mask.whole != 0) + weight += 100; + if (mat.type >= 0) + weight += (mat.index >= 0 ? 5000 : 1000); + + ItemConstraint *nct = create_new_constraint(is_craft, item, mat, mat_mask, item_quality::Ordinary, weight, NULL, string("test")); + nct->jobs.push_back(pj); + pj->constraints.push_back(nct); + } } static void compute_custom_job(ProtectedJob *pj, df::job *job) @@ -919,7 +942,7 @@ static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_mater } } -static void compute_job_outputs(color_ostream &out, ProtectedJob *pj) +static void compute_job_outputs(color_ostream &out, ProtectedJob *pj, bool create_constraint = false) { using namespace df::enums::job_type; @@ -1012,7 +1035,7 @@ static void compute_job_outputs(color_ostream &out, ProtectedJob *pj) break; } - link_job_constraint(pj, itype, isubtype, mat_mask, mat.type, mat.index); + link_job_constraint(pj, itype, isubtype, mat_mask, mat.type, mat.index, false, create_constraint); } static void map_job_constraints(color_ostream &out) @@ -1129,7 +1152,7 @@ static void map_job_items(color_ostream &out) df::item_flags bad_flags; bad_flags.whole = 0; -#define F(x) bad_flags.bits.x = true; +#define F(left_margin) bad_flags.bits.left_margin = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); F(in_building); F(construction); F(artifact1); @@ -1237,7 +1260,9 @@ static void map_job_items(color_ostream &out) } for (size_t i = 0; i < constraints.size(); i++) + { constraints[i]->computeRequest(); + } } /****************************** @@ -1673,3 +1698,1170 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet else return CR_WRONG_USAGE; } + +/****************************** + * Inventory Monitor * + ******************************/ +#define INV_MONITOR_COL_COUNT 3 +#define MAX_ITEM_NAME 15 +#define MAX_MASK 10 +#define MAX_MATERIAL 20 +#define SIDEBAR_WIDTH 30 +#define COLOR_TITLE COLOR_BLUE +#define COLOR_UNSELECTED COLOR_GREY +#define COLOR_SELECTED COLOR_WHITE +#define COLOR_HIGHLIGHTED COLOR_GREEN + +namespace wf_ui +{ + /* + * Utility Functions + */ + 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(); + } + + 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); + } + + string get_constraint_material(ItemConstraint *cv) + { + string text; + if (!cv->material.isNone()) + { + text.append(cv->material.toString()); + text.append(" "); + } + + text.append(bitfield_to_string(cv->mat_mask)); + + return text; + } + + string pad_string(string text, const int size, const bool front = true) + { + if (text.length() >= size) + return text; + + string aligned(size - text.length(), ' '); + if (front) + { + aligned.append(text); + return aligned; + } + else + { + text.append(aligned); + return text; + } + } + + /* + * Adjustment Dialog + */ + + class AdjustmentScreen + { + public: + int32_t x, y, left_margin; + + AdjustmentScreen(); + void reset(); + bool feed(set *input, ItemConstraint *cv, ProtectedJob *pj = NULL); + void render(ItemConstraint *cv); + + protected: + int32_t adjustment_ui_display_start; + + virtual void onConstraintChanged() {} + + private: + bool edit_limit, edit_gap; + string edit_string; + + }; + + AdjustmentScreen::AdjustmentScreen() + { + reset(); + } + + void AdjustmentScreen::reset() + { + edit_gap = false; + edit_limit = false; + adjustment_ui_display_start = 24; + } + + bool AdjustmentScreen::feed(set *input, ItemConstraint *cv, ProtectedJob *pj /* = NULL */) + { + if ((edit_limit || edit_gap)) + { + df::interface_key last_token = *input->rbegin(); + if (last_token == interface_key::STRING_A000) + { + // Backspace + if (edit_string.length() > 0) + { + edit_string.erase(edit_string.length()-1); + } + + return true; + } + + if (edit_string.length() >= 6) + return true; + + if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A057) + { + // Numeric character + edit_string += last_token - ascii_to_enum_offset; + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + if (input->count(interface_key::SELECT) && edit_string.length() > 0) + { + if (edit_limit) + cv->setGoalCount(atoi(edit_string.c_str())); + else + cv->setGoalGap(atoi(edit_string.c_str())); + + onConstraintChanged(); + } + edit_string.clear(); + edit_limit = false; + edit_gap = false; + } + else if (last_token == interface_key::STRING_A000) + { + // Backspace + if (edit_string.length() > 0) + { + edit_string.erase(edit_string.length()-1); + } + } + + return true; + } + else if (input->count(interface_key::CUSTOM_L)) + { + edit_string = int_to_string(cv->goalCount()); + edit_limit = true; + } + else if (input->count(interface_key::CUSTOM_G)) + { + edit_string = int_to_string(cv->goalGap()); + edit_gap = true; + } + else if (input->count(interface_key::CUSTOM_M)) + { + cv->setGoalByCount(!cv->goalByCount()); + onConstraintChanged(); + } + else if (input->count(interface_key::CUSTOM_T)) + { + if (cv) + { + // Remove tracking + if (pj) + { + for (vector::iterator it = pj->constraints.begin(); it < pj->constraints.end(); it++) + delete_constraint(*it); + + forget_job(color_ostream_proxy(Core::getInstance().getConsole()), pj); + } + } + else + { + // Add tracking + return false; + } + } + else + { + return false; + } + + return true; + } + + void AdjustmentScreen::render(ItemConstraint *cv) + { + left_margin = gps->dimx - 30; + x = left_margin; + y = adjustment_ui_display_start; + if (cv != NULL) + { + string text; + text.reserve(20); + + text.append(get_constraint_material(cv)); + if (!text.empty()) + text.append(" "); + text.append(cv->item.toString()); + + OutputString(COLOR_GREEN, x, y, text, true, left_margin); + + text.clear(); + text.append("Available: "); + text.append(int_to_string((cv->goalByCount()) ? cv->item_count : cv->item_amount)); + text.append(" "); + text.append((cv->goalByCount()) ? "Stacks" : "Items"); + + OutputString(15, x, y, text, true, left_margin); + + text.clear(); + text.append("In use : "); + text.append(int_to_string(cv->item_inuse)); + OutputString(15, x, y, text, true, left_margin); + + y += 2; + x = left_margin; + OutputHotkeyString(x, y, "Disable Tracking", "t", true, left_margin); + + text.clear(); + text.append("Limit: "); + text.append((edit_limit) ? edit_string : int_to_string(cv->goalCount())); + OutputHotkeyString(x, y, text.c_str(), "l"); + if (edit_limit) + OutputString(10, x, y, "_"); + + ++y; + x = left_margin; + text.clear(); + text.append("Gap: "); + text.append((edit_gap) ? edit_string : int_to_string(cv->goalGap())); + OutputHotkeyString(x, y, text.c_str(), "g"); + int pad = (int) (11 - text.length()); + if (edit_gap) + { + OutputString(10, x, y, "_"); + --pad; + } + + x += max(0, pad); + OutputHotkeyString(x, y, "Toggle Mode", "m"); + } + else + OutputHotkeyString(x, y, "Enable Tracking", "t", true, left_margin); + } + + + /* + * 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; + vector< ListEntry > list; + int highlighted_index; + int display_max_rows; + int display_start_offset; + bool multiselect; + bool allow_null; + + ListColumn() + { + highlighted_index = 0; + display_max_rows = gps->dimy - 4; + display_start_offset = 0; + multiselect = false; + allow_null = true; + } + + void clear() + { + list.clear(); + } + + void add(ListEntry &entry) + { + list.push_back(entry); + } + + void add(string &text, T &elem) + { + list.push_back(ListEntry(text, elem)); + } + + virtual void display_extras(const T &elem) const {} + + void display(const int left_margin, const bool is_selected_column) const + { + int 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 < list.size() && i < last_index_able_to_display; i++) + { + ++y; + UIColor fg_color = (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, list[i].text, bg_color); + display_extras(list[i].elem); + } + } + + void changeHighlight(const int highlight_change, const int offset_shift = 0) + { + highlighted_index += highlight_change + offset_shift * display_max_rows; + set_to_limit(highlighted_index, list.size() - 1); + + display_start_offset += offset_shift * display_max_rows; + set_to_limit(display_start_offset, max(0, (int)(list.size())-display_max_rows)); + + + 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; + } + + void toggleHighlighted() + { + ListEntry *entry = &list[highlighted_index]; + if (!multiselect || !allow_null) + { + int selected_count = 0; + for (size_t i = 0; i < list.size(); i++) + { + if (!multiselect && i != highlighted_index && !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() + { + vector results; + for (vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) + { + if ((*it).selected) + results.push_back((*it).elem); + } + + return results; + } + }; + + + class viewscreenChooseMaterial : public dfhack_viewscreen + { + public: + viewscreenChooseMaterial(ItemConstraint *cv = NULL); + void feed(set *input); + void render(); + + std::string getFocusString() { return "wfchoosemat"; } + + private: + ItemConstraint *cv; + + ListColumn items_column; + ListColumn masks_column; + ListColumn materials_column; + vector< ListEntry > all_masks; + + int selected_column; + + void populateItems(); + 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); + + + void changeHighlight(const int highlight_change, const int offset_shift = 0); + void changeColumn(const int amount); + void toggleHighlighted(); + }; + + viewscreenChooseMaterial::viewscreenChooseMaterial(ItemConstraint *cv /*= NULL*/) + { + this->cv = cv; + selected_column = 0; + items_column.title = "Item"; + items_column.allow_null = false; + masks_column.title = "Type"; + masks_column.multiselect = true; + materials_column.title = "Material"; + + populateItems(); + items_column.list[0].selected = true; + + 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(cv != NULL); + populateMaterials(cv != NULL); + } + + void viewscreenChooseMaterial::populateItems() + { + items_column.clear(); + if (cv != NULL) + { + items_column.add(cv->item.toString(), cv->item); + } + } + + 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) + { + if (cv->mat_mask.whole & entry.elem.whole) + entry.selected = true; + } + masks_column.add(entry); + } + } + + 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); + } + + for (size_t i = 0; i < raws.plants.all.size(); i++) + { + df::plant_raw *p = raws.plants.all[i]; + string basename = p->name; + + MaterialInfo material; + material.decode(p->material_defs.type_basic_mat, p->material_defs.idx_basic_mat); + if (!selected_category.whole || material.matches(selected_category)) + { + ListEntry entry(pad_string(basename+" (all)", MAX_MATERIAL, false), material); + if (set_defaults) + { + if (cv->material.matches(material)) + entry.selected = true; + } + materials_column.add(entry); + + for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++) + { + MaterialInfo material; + material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); + if (addMaterialEntry(selected_category, material, basename+" (" + p->material[j]->id + "): " + material.toString(), set_defaults)) + entry.selected = false; + } + } + } + + for (size_t i = 0; i < raws.creatures.all.size(); i++) + { + df::creature_raw *p = raws.creatures.all[i]; + string basename = p->name[0]; + + for (size_t j = 0; j < p->material.size(); j++) + { + MaterialInfo material; + material.decode(DFHack::MaterialInfo::CREATURE_BASE+j, i); + addMaterialEntry(selected_category, material, basename+" (" + p->material[j]->id + "): " + material.toString(), set_defaults); + } + } + } + + + 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; + } + } + materials_column.add(entry); + } + + return selected; + } + + void viewscreenChooseMaterial::feed(set *input) + { + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + else if (input->count(interface_key::CURSOR_UP)) + { + changeHighlight(-1); + } + else if (input->count(interface_key::CURSOR_DOWN)) + { + changeHighlight(1); + } + else if (input->count(interface_key::CURSOR_LEFT)) + { + changeColumn(-1); + } + else if (input->count(interface_key::CURSOR_RIGHT)) + { + changeColumn(1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) + { + changeHighlight(0, -1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) + { + changeHighlight(0, 1); + } + else if (input->count(interface_key::SELECT)) + { + toggleHighlighted(); + } + } + + void viewscreenChooseMaterial::render() + { + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Workflow Material "); + + int x = 2; + items_column.display(x, selected_column == 0); + + x += MAX_ITEM_NAME + 1; + masks_column.display(x, selected_column == 1); + + x += MAX_MASK + 1; + materials_column.display(x, selected_column == 2); + + } + + void viewscreenChooseMaterial::changeHighlight(const int highlight_change, const int offset_shift /* = 0 */) + { + switch (selected_column) + { + case 0: + items_column.changeHighlight(highlight_change, offset_shift); + break; + case 1: + masks_column.changeHighlight(highlight_change, offset_shift); + break; + case 2: + materials_column.changeHighlight(highlight_change, offset_shift); + break; + } + } + + void viewscreenChooseMaterial::changeColumn(const int amount) + { + selected_column += amount; + set_to_limit(selected_column, 2); + } + + void viewscreenChooseMaterial::toggleHighlighted() + { + switch (selected_column) + { + case 0: + items_column.toggleHighlighted(); + break; + case 1: + masks_column.toggleHighlighted(); + populateMaterials(false); + break; + case 2: + materials_column.toggleHighlighted(); + break; + } + } + + + /* + * Inventory Monitor + */ + class viewscreenInventoryMonitor : public dfhack_viewscreen, public AdjustmentScreen + { + private: + struct TableRow + { + string texts[INV_MONITOR_COL_COUNT]; + int8_t colors[INV_MONITOR_COL_COUNT]; + + vector< pair > history_plot; + int32_t limit_y, gap_y; + + ItemConstraint *cv; + }; + + + public: + const static int column_widths[]; + const static string column_titles[]; + const static int title_row; + + viewscreenInventoryMonitor(); + + void feed(set *input); + + void render(); + + std::string getFocusString() { return "invmonitor"; } + + virtual void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + init(); + } + + virtual void onConstraintChanged(); + + private: + vector rows; + + int bottom_controls_row; + int table_max_rows; + int table_start_offset; + int selected_row; + + int32_t divider_x; + int chart_width, chart_height; + int32_t axis_y_end, axis_y_start, axis_x_start, axis_x_end; + + void init(); + + void changeSelection(const int amount); + + static bool compareConstraints(TableRow const& a, TableRow const& b) + { + return a.texts[0].compare(b.texts[0]) < 0; + } + }; + + const int viewscreenInventoryMonitor::title_row = 2; + + const int viewscreenInventoryMonitor::column_widths[] = + {MAX_ITEM_NAME, 20, 21}; + + const string viewscreenInventoryMonitor::column_titles[] = + {"Item", "Material", "Stock / Limit"}; + + + viewscreenInventoryMonitor::viewscreenInventoryMonitor() + { + adjustment_ui_display_start = 2; + selected_row = 0; + chart_width = SIDEBAR_WIDTH - 2; + + init(); + } + + void viewscreenInventoryMonitor::init() + { + bottom_controls_row = gps->dimy - 2; + table_max_rows = bottom_controls_row - 4; + table_start_offset = 0; + + divider_x = gps->dimx - SIDEBAR_WIDTH - 2; + chart_height = min(SIDEBAR_WIDTH, gps->dimy - 20); + axis_y_end = gps->dimy - 3; + axis_y_start = axis_y_end - chart_height; + axis_x_start = divider_x + 2; + axis_x_end = axis_x_start + chart_width - 1; + + rows.clear(); + for (vector::iterator it = constraints.begin(); it < constraints.end(); it++) + { + TableRow row; + row.cv = *it; + + row.texts[0] = row.cv->item.toString(); + row.colors[0] = COLOR_UNSELECTED; + + row.texts[1] = get_constraint_material(row.cv); + row.colors[1] = COLOR_UNSELECTED; + + string text; + text.append(int_to_string((row.cv->goalByCount()) ? row.cv->item_count : row.cv->item_amount)); + text.append(" "); + text.append((row.cv->goalByCount()) ? "S " : "I "); + text = pad_string(text, 9); + text.append(int_to_string(row.cv->goalCount())); + row.texts[2] = text; + row.colors[2] = COLOR_UNSELECTED; + + if (max_history_days > 0) + { + row.history_plot.clear(); + int max_val = *max_element(row.cv->history.begin(), row.cv->history.end()); + max_val = max(max_val, row.cv->goalCount()); + float scale_y = (float) chart_height / (float) max_val; + float scale_x = (float) chart_width / (float) (max_history_days * 2); + + row.limit_y = axis_y_end - (int) (scale_y * (float) row.cv->goalCount()); + row.gap_y = axis_y_end - (int) (scale_y * (float) (row.cv->goalCount() - row.cv->goalGap())); + + for (size_t i = 0; i < row.cv->history.size(); i++) + { + pair point(axis_x_start + (int) (scale_x * (float) i), + axis_y_end - (int) (scale_y * (float) row.cv->history[i])); + + row.history_plot.push_back(point); + } + } + + rows.push_back(row); + } + + sort(rows.begin(), rows.end(), &viewscreenInventoryMonitor::compareConstraints); + changeSelection(0); + } + + void viewscreenInventoryMonitor::onConstraintChanged() + { + init(); + } + + void viewscreenInventoryMonitor::render() + { + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Inventory Monitor "); + + Screen::Pen border('\xDB', 8); + for (int32_t y = 1; y < gps->dimy - 1; y++) + { + paintTile(border, divider_x, y); + } + + int32_t x = 2; + int32_t y = title_row; + + + for (int column = 0; column < INV_MONITOR_COL_COUNT; column++) + { + paint_text(COLOR_TITLE, x, y, column_titles[column]); + x += column_widths[column]; + } + + int last_row_able_to_display = table_start_offset + table_max_rows; + for (int row = table_start_offset; row < last_row_able_to_display && row < rows.size(); row++) + { + x = 2; + ++y; + for (int column = 0; column < INV_MONITOR_COL_COUNT; column++) + { + int8_t color = rows[row].colors[column]; + if (column < 2 && row == selected_row) + color = COLOR_SELECTED; + paint_text(color, x, y, rows[row].texts[column]); + x += column_widths[column]; + } + } + + y = bottom_controls_row; + x = 2; + OutputHotkeyString(x, y, "Edit", "e"); + + if (rows.size() > 0) + { + TableRow row = rows[selected_row]; + AdjustmentScreen::render(row.cv); + + if (max_history_days > 0) + { + Screen::Pen y_axis('\xB3', COLOR_BROWN); + x = axis_x_start; + for (y = axis_y_start; y <= axis_y_end; y++) + { + paintTile(y_axis, x, y); + } + + Screen::Pen up_arrow('\xCF', COLOR_BROWN); + paintTile(up_arrow, axis_x_start, axis_y_start-1); + + Screen::Pen x_axis('\xC4', COLOR_BROWN); + y = axis_y_end; + for (x = axis_x_start; x <= axis_x_end; x++) + { + paintTile(x_axis, x, y); + paint_text(COLOR_LIGHTGREEN, x, row.limit_y, "-"); + paint_text(COLOR_GREEN, x, row.gap_y, "-"); + } + + Screen::Pen right_arrow('\xAF', COLOR_BROWN); + paintTile(right_arrow, axis_x_end+1, axis_y_end); + + Screen::Pen zero_axis('\x9E', COLOR_BROWN); + paintTile(zero_axis, axis_x_start, axis_y_end); + + for (size_t i = 0; i < row.history_plot.size(); i++) + { + int x = row.history_plot[i].first; + int y = row.history_plot[i].second; + paint_text(COLOR_CYAN, x, y, "*"); + } + } + } + + + } + + void viewscreenInventoryMonitor::feed(set *input) + { + if (rows.size() > 0 && AdjustmentScreen::feed(input, rows[selected_row].cv)) + return; + + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + else if (input->count(interface_key::CURSOR_UP)) + { + changeSelection(-1); + } + else if (input->count(interface_key::CURSOR_DOWN)) + { + changeSelection(1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) + { + table_start_offset = max(table_start_offset-table_max_rows, 0); + changeSelection(-table_max_rows); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) + { + table_start_offset = min(table_start_offset+table_max_rows, (int)(rows.size())-table_max_rows); + changeSelection(table_max_rows); + } + else if (input->count(interface_key::CUSTOM_E)) + { + Screen::show(new viewscreenChooseMaterial(rows[selected_row].cv)); + } + } + + void viewscreenInventoryMonitor::changeSelection(int amount) + { + selected_row += amount; + set_to_limit(selected_row, rows.size() - 1); + + if (selected_row < table_start_offset) + table_start_offset = selected_row; + else if (selected_row >= table_start_offset + table_max_rows) + table_start_offset = selected_row - table_max_rows + 1; + } + + /****************************** + * Hook for workshop view * + ******************************/ + struct wf_workshop_hook : public df::viewscreen_dwarfmodest + { + typedef df::viewscreen_dwarfmodest interpose_base; + + static color_ostream_proxy console_out; + static df::job *last_job; + static df::job *job; + static AdjustmentScreen dialog; + + bool checkJobSelection() + { + if (!enabled) + return NULL; + + if (!first_update_done) + { + plugin_onupdate(console_out); + } + + job = Gui::getSelectedWorkshopJob(console_out, true); + if (job != last_job) + { + dialog.reset(); + last_job = job; + } + + + return job != NULL; + } + + bool handleInput(set *input) + { + bool key_processed = true; + if (checkJobSelection()) + { + ProtectedJob *pj = get_known(job->id); + if (!pj) + return false; + + ItemConstraint *cv = NULL; + if (pj->constraints.size() > 0) + cv = pj->constraints[0]; + + if (!dialog.feed(input, cv, pj)) + { + if (input->count(interface_key::CUSTOM_T)) + { + if (!cv) + { + // Add tracking + job->flags.bits.repeat = true; + compute_job_outputs(console_out, pj, true); + cv = pj->constraints[0]; + cv->setGoalByCount(false); + cv->setGoalCount(10); + cv->setGoalGap(1); + } + } + else if (input->count(interface_key::CUSTOM_I)) + { + Screen::show(new viewscreenInventoryMonitor()); + } + else + key_processed = false; + } + } + else + key_processed = false; + + return key_processed; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); + else + input->clear(); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + if (checkJobSelection()) + { + ProtectedJob *pj = get_known(job->id); + if (!pj) + { + pj = add_known_job(job); + compute_job_outputs(console_out, pj); + } + + ItemConstraint *cv = NULL; + if (pj->constraints.size() > 0) + { + cv = pj->constraints[0]; + } + dialog.render(cv); + ++dialog.y; + OutputHotkeyString(dialog.left_margin, dialog.y, "Inventory Monitor", "i"); + } + } + }; + + color_ostream_proxy wf_workshop_hook::console_out(Core::getInstance().getConsole()); + df::job *wf_workshop_hook::job = NULL; + df::job *wf_workshop_hook::last_job = NULL; + AdjustmentScreen wf_workshop_hook::dialog; + + + IMPLEMENT_VMETHOD_INTERPOSE(wf_workshop_hook, feed); + IMPLEMENT_VMETHOD_INTERPOSE(wf_workshop_hook, render); +} + +#undef INV_MONITOR_COL_COUNT +#undef MAX_ITEM_NAME + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + if (!world || !ui) + return CR_FAILURE; + + if (!gps || !INTERPOSE_HOOK(wf_ui::wf_workshop_hook, feed).apply() || !INTERPOSE_HOOK(wf_ui::wf_workshop_hook, render).apply()) + out.printerr("Could not insert Workflow hooks!\n"); + + if (ui_workshop_job_cursor && job_next_id) { + commands.push_back( + PluginCommand( + "workflow", "Manage control of repeat jobs.", + workflow_cmd, false, + " workflow enable [option...]\n" + " workflow disable [option...]\n" + " If no options are specified, enables or disables the plugin.\n" + " Otherwise, enables or disables any of the following options:\n" + " - drybuckets: Automatically empty abandoned water buckets.\n" + " - auto-melt: Resume melt jobs when there are objects to melt.\n" + " workflow jobs\n" + " List workflow-controlled jobs (if in a workshop, filtered by it).\n" + " workflow list\n" + " List active constraints, and their job counts.\n" + " workflow list-commands\n" + " List workflow commands that re-create existing constraints.\n" + " workflow count [cnt-gap]\n" + " workflow amount [cnt-gap]\n" + " Set a constraint. The first form counts each stack as only 1 item.\n" + " workflow unlimit \n" + " Delete a constraint.\n" + " workflow unlimit-all\n" + " Delete all constraints.\n" + "Function:\n" + " - When the plugin is enabled, it protects all repeat jobs from removal.\n" + " If they do disappear due to any cause, they are immediately re-added\n" + " to their workshop and suspended.\n" + " - In addition, when any constraints on item amounts are set, repeat jobs\n" + " that produce that kind of item are automatically suspended and resumed\n" + " as the item amount goes above or below the limit. The gap specifies how\n" + " much below the limit the amount has to drop before jobs are resumed;\n" + " this is intended to reduce the frequency of jobs being toggled.\n" + "Constraint examples:\n" + " workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100\n" + " workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50\n" + " Keep metal bolts within 900-1000, and wood/bone within 150-200.\n" + " workflow count FOOD 120 30\n" + " workflow count DRINK 120 30\n" + " Keep the number of prepared food & drink stacks between 90 and 120\n" + " workflow count BIN 30\n" + " workflow count BARREL 30\n" + " workflow count BOX/CLOTH,SILK,YARN 30\n" + " Make sure there are always 25-30 empty bins/barrels/bags.\n" + " workflow count BAR//COAL 20\n" + " workflow count BAR//COPPER 30\n" + " Make sure there are always 15-20 coal and 25-30 copper bars.\n" + " workflow count CRAFTS//GOLD 20\n" + " Produce 15-20 gold crafts.\n" + " workflow count POWDER_MISC/SAND 20\n" + " workflow count BOULDER/CLAY 20\n" + " Collect 15-20 sand bags and clay boulders.\n" + " workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20\n" + " Make sure there are always 80-100 units of dimple dye.\n" + " In order for this to work, you have to set the material of\n" + " the PLANT input on the Mill Plants job to MUSHROOM_CUP_DIMPLE\n" + " using the 'job item-material' command.\n" + ) + ); + } + + init_state(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + INTERPOSE_HOOK(wf_ui::wf_workshop_hook, feed).remove(); + INTERPOSE_HOOK(wf_ui::wf_workshop_hook, render).remove(); + + cleanup_state(out); + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + cleanup_state(out); + init_state(out); + break; + case SC_MAP_UNLOADED: + cleanup_state(out); + break; + default: + break; + } + + return CR_OK; +} + From 69bb6a5b3ba008137deea2ebf2e911a5f2a11007 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 6 Nov 2012 07:48:24 +1300 Subject: [PATCH 002/138] Finished dashboard, colour coding, trend calculation and charts, material selection screen, mouse support --- plugins/workflow.cpp | 1600 +++++++++++++++++++++++++++--------------- 1 file changed, 1025 insertions(+), 575 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 345dfa578..0cf3b1c8f 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -60,8 +60,12 @@ #include #include #include "df/creature_raw.h" -using df::global::gps; +#include "df/enabler.h" + using std::deque; +using df::global::gps; +using df::global::enabler; + using std::vector; using std::string; @@ -433,6 +437,7 @@ static void init_state(color_ostream &out) start_protect(out); } +static bool first_update_done = false; static void enable_plugin(color_ostream &out) { auto pworld = Core::getInstance().getWorld(); @@ -445,9 +450,15 @@ static void enable_plugin(color_ostream &out) setOptionEnabled(CF_ENABLED, true); enabled = true; - out << "Enabling the plugin." << endl; + out << "Enabling workflow plugin." << endl; + + for (vector::iterator it = constraints.begin(); it != constraints.end(); it++) + { + (*it)->history.clear(); + } start_protect(out); + first_update_done = false; } /****************************** @@ -578,23 +589,6 @@ static void recover_jobs(color_ostream &out) static void process_constraints(color_ostream &out); -#define ITEMDEF_VECTORS \ - ITEM(WEAPON, weapons, itemdef_weaponst) \ - ITEM(TRAPCOMP, trapcomps, itemdef_trapcompst) \ - ITEM(TOY, toys, itemdef_toyst) \ - ITEM(TOOL, tools, itemdef_toolst) \ - ITEM(INSTRUMENT, instruments, itemdef_instrumentst) \ - ITEM(ARMOR, armor, itemdef_armorst) \ - ITEM(AMMO, ammo, itemdef_ammost) \ - ITEM(SIEGEAMMO, siege_ammo, itemdef_siegeammost) \ - ITEM(GLOVES, gloves, itemdef_glovesst) \ - ITEM(SHOES, shoes, itemdef_shoesst) \ - ITEM(SHIELD, shields, itemdef_shieldst) \ - ITEM(HELM, helms, itemdef_helmst) \ - ITEM(PANTS, pants, itemdef_pantsst) \ - ITEM(FOOD, food, itemdef_foodst) - -static bool first_update_done = false; DFhackCExport command_result plugin_onupdate(color_ostream &out) { if (!enabled) @@ -602,7 +596,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) // Every 5 frames check the jobs for disappearance static unsigned cnt = 0; - if ((++cnt % 5) != 0 && !first_update_done) + if ((++cnt % 5) != 0 && first_update_done) return CR_OK; check_lost_jobs(out, world->frame_counter - last_tick_frame_count); @@ -637,10 +631,42 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) * ITEM COUNT CONSTRAINT * ******************************/ +static string get_constraint_material(ItemConstraint *cv) +{ + string text; + if (!cv->material.isNone()) + { + text.append(cv->material.toString()); + text.append(" "); + } + + text.append(bitfield_to_string(cv->mat_mask)); + + return text; +} + static ItemConstraint * create_new_constraint(bool is_craft, ItemTypeInfo item, MaterialInfo material, df::dfhack_material_category mat_mask, item_quality::item_quality minqual, - int weight, PersistentDataItem * cfg, const std::string & str) + PersistentDataItem * cfg, std::string str) { + for (size_t i = 0; i < constraints.size(); i++) + { + ItemConstraint *ct = constraints[i]; + if (ct->is_craft == is_craft && + ct->item == item && ct->material == material && + ct->mat_mask.whole == mat_mask.whole && + ct->min_quality == minqual) + return ct; + } + + int weight = 0; + if (item.subtype >= 0) + weight += 10000; + if (mat_mask.whole != 0) + weight += 100; + if (material.type >= 0) + weight += (material.index >= 0 ? 5000 : 1000); + ItemConstraint *nct = new ItemConstraint; nct->is_craft = is_craft; nct->item = item; @@ -653,6 +679,12 @@ static ItemConstraint * create_new_constraint(bool is_craft, ItemTypeInfo item, nct->config = *cfg; else { + if (str.empty()) + { + str = get_constraint_material(nct); + str.append(item.toString()); + } + nct->config = Core::getInstance().getWorld()->AddPersistentData("workflow/constraints"); nct->init(str); } @@ -669,8 +701,6 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str if (tokens.size() > 4) return NULL; - int weight = 0; - bool is_craft = false; ItemTypeInfo item; @@ -681,9 +711,6 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str return NULL; } - if (item.subtype >= 0) - weight += 10000; - df::dfhack_material_category mat_mask; std::string maskstr = vector_get(tokens,1); if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { @@ -691,9 +718,6 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str return NULL; } - if (mat_mask.whole != 0) - weight += 100; - MaterialInfo material; std::string matstr = vector_get(tokens,2); if (!matstr.empty() && (!material.find(matstr) || !material.isValid())) { @@ -716,25 +740,12 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str } } - if (material.type >= 0) - weight += (material.index >= 0 ? 5000 : 1000); - if (mat_mask.whole && material.isValid() && !material.matches(mat_mask)) { out.printerr("Material %s doesn't match mask %s\n", matstr.c_str(), maskstr.c_str()); return NULL; } - for (size_t i = 0; i < constraints.size(); i++) - { - ItemConstraint *ct = constraints[i]; - if (ct->is_craft == is_craft && - ct->item == item && ct->material == material && - ct->mat_mask.whole == mat_mask.whole && - ct->min_quality == minqual) - return ct; - } - - ItemConstraint *nct = create_new_constraint(is_craft, item, material, mat_mask, minqual, weight, cfg, str); + ItemConstraint *nct = create_new_constraint(is_craft, item, material, mat_mask, minqual, cfg, str); return nct; } @@ -743,7 +754,15 @@ static void delete_constraint(ItemConstraint *cv) { int idx = linear_index(constraints, cv); if (idx >= 0) + { + for (vector::iterator it = constraints[idx]->jobs.begin(); it != constraints[idx]->jobs.end(); it++) + { + int idpj = linear_index((*it)->constraints, cv); + if (idpj >= 0) + vector_erase_at((*it)->constraints, idpj); + } vector_erase_at(constraints, idx); + } Core::getInstance().getWorld()->DeletePersistentData(cv->config); delete cv; @@ -822,15 +841,7 @@ static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t i item.type = itype; item.subtype = isubtype; - int weight = 0; - if (item.subtype >= 0) - weight += 10000; - if (mat_mask.whole != 0) - weight += 100; - if (mat.type >= 0) - weight += (mat.index >= 0 ? 5000 : 1000); - - ItemConstraint *nct = create_new_constraint(is_craft, item, mat, mat_mask, item_quality::Ordinary, weight, NULL, string("test")); + ItemConstraint *nct = create_new_constraint(is_craft, item, mat, mat_mask, item_quality::Ordinary, NULL, ""); nct->jobs.push_back(pj); pj->constraints.push_back(nct); } @@ -1510,322 +1521,143 @@ static void print_job(color_ostream &out, ProtectedJob *pj) print_constraint(out, pj->constraints[i], true, " "); } -static command_result workflow_cmd(color_ostream &out, vector & parameters) +/****************************** + * Inventory Monitor * + ******************************/ +#define MAX_ITEM_NAME 17 +#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 + +#define ITEMDEF_VECTORS \ + ITEM(WEAPON, weapons, itemdef_weaponst) \ + ITEM(TRAPCOMP, trapcomps, itemdef_trapcompst) \ + ITEM(TOY, toys, itemdef_toyst) \ + ITEM(TOOL, tools, itemdef_toolst) \ + ITEM(INSTRUMENT, instruments, itemdef_instrumentst) \ + ITEM(ARMOR, armor, itemdef_armorst) \ + ITEM(AMMO, ammo, itemdef_ammost) \ + ITEM(SIEGEAMMO, siege_ammo, itemdef_siegeammost) \ + ITEM(GLOVES, gloves, itemdef_glovesst) \ + ITEM(SHOES, shoes, itemdef_shoesst) \ + ITEM(SHIELD, shields, itemdef_shieldst) \ + ITEM(HELM, helms, itemdef_helmst) \ + ITEM(PANTS, pants, itemdef_pantsst) \ + ITEM(FOOD, food, itemdef_foodst) + +namespace wf_ui { - CoreSuspender suspend; + /* + * Utility Functions + */ + typedef int8_t UIColor; - if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); - return CR_FAILURE; - } + const int ascii_to_enum_offset = interface_key::STRING_A048 - '0'; - if (enabled) { - check_lost_jobs(out, 0); - recover_jobs(out); - update_job_data(out); - map_job_constraints(out); - map_job_items(out); + inline string int_to_string(const int n) + { + return static_cast( &(ostringstream() << n) )->str(); } - df::building *workshop = NULL; - //FIXME: unused variable! - //df::job *job = NULL; - - if (Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && - ui->main.mode == ui_sidebar_mode::QueryBuilding) + static void set_to_limit(int &value, const int maximum, const int min = 0) { - workshop = world->selected_building; - //job = Gui::getSelectedWorkshopJob(out, true); + if (value < min) + value = min; + else if (value > maximum) + value = maximum; } - std::string cmd = parameters.empty() ? "list" : parameters[0]; + 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); + } - if (cmd == "enable" || cmd == "disable") + static string pad_string(string text, const int size, const bool front = true, const bool trim = false) { - bool enable = (cmd == "enable"); - if (enable && !enabled) - { - enable_plugin(out); - } - else if (!enable && parameters.size() == 1) + if (text.length() > size) { - if (enabled) + if (trim && size > 10) { - enabled = false; - setOptionEnabled(CF_ENABLED, false); - stop_protect(out); + text = text.substr(0, size-3); + text.append("..."); } - - out << "The plugin is disabled." << endl; - return CR_OK; + return text; } - for (size_t i = 1; i < parameters.size(); i++) + string aligned(size - text.length(), ' '); + if (front) { - if (parameters[i] == "drybuckets") - setOptionEnabled(CF_DRYBUCKETS, enable); - else if (parameters[i] == "auto-melt") - setOptionEnabled(CF_AUTOMELT, enable); - else - return CR_WRONG_USAGE; + aligned.append(text); + return aligned; } - - if (enabled) - out << "The plugin is enabled." << endl; else - out << "The plugin is disabled." << endl; + { + text.append(aligned); + return text; + } + } - if (isOptionEnabled(CF_DRYBUCKETS)) - out << "Option drybuckets is enabled." << endl; - if (isOptionEnabled(CF_AUTOMELT)) - out << "Option auto-melt is enabled." << endl; + /* + * Adjustment Dialog + */ - return CR_OK; - } - else if (cmd == "count" || cmd == "amount") + class AdjustmentScreen { - if (!enabled) - enable_plugin(out); - } + public: + int32_t x, y, left_margin; - if (!enabled) - out << "Note: the plugin is not enabled." << endl; + AdjustmentScreen(); + void reset(); + bool feed(set *input, ItemConstraint *cv, ProtectedJob *pj = NULL); + void render(ItemConstraint *cv); - if (cmd == "jobs") + protected: + int32_t adjustment_ui_display_start; + + virtual void onConstraintChanged() {} + virtual void onModeChanged() {} + + private: + bool edit_limit, edit_gap; + string edit_string; + + }; + + AdjustmentScreen::AdjustmentScreen() { - if (workshop) - { - for (size_t i = 0; i < workshop->jobs.size(); i++) - print_job(out, get_known(workshop->jobs[i]->id)); - } - else - { - for (TKnownJobs::iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) - if (it->second->isLive()) - print_job(out, it->second); - } + reset(); + } - bool pending = false; + void AdjustmentScreen::reset() + { + edit_gap = false; + edit_limit = false; + adjustment_ui_display_start = 24; + } - for (size_t i = 0; i < pending_recover.size(); i++) + bool AdjustmentScreen::feed(set *input, ItemConstraint *cv, ProtectedJob *pj /* = NULL */) + { + if ((edit_limit || edit_gap)) { - if (!workshop || pending_recover[i]->holder == workshop) + df::interface_key last_token = *input->rbegin(); + if (last_token == interface_key::STRING_A000) { - if (!pending) + // Backspace + if (edit_string.length() > 0) { - out.print("\nPending recovery:\n"); - pending = true; + edit_string.erase(edit_string.length()-1); } - Job::printJobDetails(out, pending_recover[i]->job_copy); + return true; } - } - return CR_OK; - } - else if (cmd == "list") - { - for (size_t i = 0; i < constraints.size(); i++) - print_constraint(out, constraints[i]); - - return CR_OK; - } - else if (cmd == "list-commands") - { - for (size_t i = 0; i < constraints.size(); i++) - { - auto cv = constraints[i]; - out << "workflow " << (cv->goalByCount() ? "count " : "amount ") - << cv->config.val() << " " << cv->goalCount() << " " << cv->goalGap() << endl; - } - - return CR_OK; - } - else if (cmd == "count" || cmd == "amount") - { - if (parameters.size() < 3) - return CR_WRONG_USAGE; - - int limit = atoi(parameters[2].c_str()); - if (limit <= 0) { - out.printerr("Invalid limit value.\n"); - return CR_FAILURE; - } - - ItemConstraint *icv = get_constraint(out, parameters[1]); - if (!icv) - return CR_FAILURE; - - icv->setGoalByCount(cmd == "count"); - icv->setGoalCount(limit); - if (parameters.size() >= 4) - icv->setGoalGap(atoi(parameters[3].c_str())); - else - icv->setGoalGap(-1); - - process_constraints(out); - print_constraint(out, icv); - return CR_OK; - } - else if (cmd == "unlimit") - { - if (parameters.size() != 2) - return CR_WRONG_USAGE; - - for (size_t i = 0; i < constraints.size(); i++) - { - if (constraints[i]->config.val() != parameters[1]) - continue; - - delete_constraint(constraints[i]); - return CR_OK; - } - - out.printerr("Constraint not found: %s\n", parameters[1].c_str()); - return CR_FAILURE; - } - else if (cmd == "unlimit-all") - { - if (parameters.size() != 1) - return CR_WRONG_USAGE; - - while (!constraints.empty()) - delete_constraint(constraints[0]); - - out.print("Removed all constraints.\n"); - return CR_OK; - } - else - return CR_WRONG_USAGE; -} - -/****************************** - * Inventory Monitor * - ******************************/ -#define INV_MONITOR_COL_COUNT 3 -#define MAX_ITEM_NAME 15 -#define MAX_MASK 10 -#define MAX_MATERIAL 20 -#define SIDEBAR_WIDTH 30 -#define COLOR_TITLE COLOR_BLUE -#define COLOR_UNSELECTED COLOR_GREY -#define COLOR_SELECTED COLOR_WHITE -#define COLOR_HIGHLIGHTED COLOR_GREEN - -namespace wf_ui -{ - /* - * Utility Functions - */ - 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(); - } - - 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); - } - - string get_constraint_material(ItemConstraint *cv) - { - string text; - if (!cv->material.isNone()) - { - text.append(cv->material.toString()); - text.append(" "); - } - - text.append(bitfield_to_string(cv->mat_mask)); - - return text; - } - - string pad_string(string text, const int size, const bool front = true) - { - if (text.length() >= size) - return text; - - string aligned(size - text.length(), ' '); - if (front) - { - aligned.append(text); - return aligned; - } - else - { - text.append(aligned); - return text; - } - } - - /* - * Adjustment Dialog - */ - - class AdjustmentScreen - { - public: - int32_t x, y, left_margin; - - AdjustmentScreen(); - void reset(); - bool feed(set *input, ItemConstraint *cv, ProtectedJob *pj = NULL); - void render(ItemConstraint *cv); - - protected: - int32_t adjustment_ui_display_start; - - virtual void onConstraintChanged() {} - - private: - bool edit_limit, edit_gap; - string edit_string; - - }; - - AdjustmentScreen::AdjustmentScreen() - { - reset(); - } - - void AdjustmentScreen::reset() - { - edit_gap = false; - edit_limit = false; - adjustment_ui_display_start = 24; - } - - bool AdjustmentScreen::feed(set *input, ItemConstraint *cv, ProtectedJob *pj /* = NULL */) - { - if ((edit_limit || edit_gap)) - { - df::interface_key last_token = *input->rbegin(); - if (last_token == interface_key::STRING_A000) - { - // Backspace - if (edit_string.length() > 0) - { - edit_string.erase(edit_string.length()-1); - } - - return true; - } - - if (edit_string.length() >= 6) - return true; + if (edit_string.length() >= 6) + return true; if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A057) { @@ -1868,10 +1700,10 @@ namespace wf_ui edit_string = int_to_string(cv->goalGap()); edit_gap = true; } - else if (input->count(interface_key::CUSTOM_M)) + else if (input->count(interface_key::CUSTOM_N)) { cv->setGoalByCount(!cv->goalByCount()); - onConstraintChanged(); + onModeChanged(); } else if (input->count(interface_key::CUSTOM_T)) { @@ -1885,12 +1717,16 @@ namespace wf_ui forget_job(color_ostream_proxy(Core::getInstance().getConsole()), pj); } + else + delete_constraint(cv); } else { // Add tracking return false; } + + onConstraintChanged(); } else { @@ -1905,19 +1741,12 @@ namespace wf_ui left_margin = gps->dimx - 30; x = left_margin; y = adjustment_ui_display_start; + OutputString(COLOR_BROWN, x, y, "Workflow Settings", true, left_margin); if (cv != NULL) { string text; text.reserve(20); - text.append(get_constraint_material(cv)); - if (!text.empty()) - text.append(" "); - text.append(cv->item.toString()); - - OutputString(COLOR_GREEN, x, y, text, true, left_margin); - - text.clear(); text.append("Available: "); text.append(int_to_string((cv->goalByCount()) ? cv->item_count : cv->item_amount)); text.append(" "); @@ -1930,12 +1759,9 @@ namespace wf_ui text.append(int_to_string(cv->item_inuse)); OutputString(15, x, y, text, true, left_margin); - y += 2; - x = left_margin; - OutputHotkeyString(x, y, "Disable Tracking", "t", true, left_margin); - + ++y; text.clear(); - text.append("Limit: "); + text.append("Limit : "); text.append((edit_limit) ? edit_string : int_to_string(cv->goalCount())); OutputHotkeyString(x, y, text.c_str(), "l"); if (edit_limit) @@ -1944,18 +1770,16 @@ namespace wf_ui ++y; x = left_margin; text.clear(); - text.append("Gap: "); + text.append("Gap : "); text.append((edit_gap) ? edit_string : int_to_string(cv->goalGap())); OutputHotkeyString(x, y, text.c_str(), "g"); - int pad = (int) (11 - text.length()); if (edit_gap) - { OutputString(10, x, y, "_"); - --pad; - } - x += max(0, pad); - OutputHotkeyString(x, y, "Toggle Mode", "m"); + ++y; + x = left_margin; + OutputHotkeyString(x, y, "Disable Tracking", "t", true, left_margin); + OutputHotkeyString(x, y, (cv->goalByCount()) ? "Count Items" : "Count Stacks", "n"); } else OutputHotkeyString(x, y, "Enable Tracking", "t", true, left_margin); @@ -1986,79 +1810,181 @@ namespace wf_ui { public: string title; - vector< ListEntry > list; int highlighted_index; - int display_max_rows; 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; - display_max_rows = gps->dimy - 4; - display_start_offset = 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(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) const {} + virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {} - void display(const int left_margin, const bool is_selected_column) const + void display(const bool is_selected_column) const { - int y = 2; + 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 < list.size() && i < last_index_able_to_display; i++) + for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++) { ++y; - UIColor fg_color = (list[i].selected) ? COLOR_SELECTED : COLOR_UNSELECTED; + 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, list[i].text, bg_color); - display_extras(list[i].elem); + 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 changeHighlight(const int highlight_change, const int offset_shift = 0) + void filter_display() { - highlighted_index += highlight_change + offset_shift * display_max_rows; - set_to_limit(highlighted_index, list.size() - 1); + 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); + } - display_start_offset += offset_shift * display_max_rows; - set_to_limit(display_start_offset, max(0, (int)(list.size())-display_max_rows)); + 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 (vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) + { + it->selected = false; + } + } + + return true; } void toggleHighlighted() { - ListEntry *entry = &list[highlighted_index]; + 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 && i != highlighted_index && !entry->selected) + if (!multiselect && !entry->selected) list[i].selected = false; if (!allow_null && list[i].selected) selected_count++; @@ -2071,23 +1997,171 @@ namespace wf_ui entry->selected = !entry->selected; } - vector getSelectedElems() + vector getSelectedElems(bool only_one = false) { - vector results; + vector results; for (vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) { if ((*it).selected) - results.push_back((*it).elem); + { + 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; + }; class viewscreenChooseMaterial : public dfhack_viewscreen { public: + static bool reset_list; + viewscreenChooseMaterial(ItemConstraint *cv = NULL); void feed(set *input); void render(); @@ -2111,24 +2185,32 @@ namespace wf_ui 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 changeHighlight(const int highlight_change, const int offset_shift = 0); - void changeColumn(const int amount); - void toggleHighlighted(); + void validateColumn(); }; + bool viewscreenChooseMaterial::reset_list = false; + + viewscreenChooseMaterial::viewscreenChooseMaterial(ItemConstraint *cv /*= NULL*/) { this->cv = cv; selected_column = 0; items_column.title = "Item"; items_column.allow_null = false; + items_column.multiselect = false; + items_column.auto_select = true; + items_column.force_sort = true; masks_column.title = "Type"; masks_column.multiselect = true; + masks_column.left_margin = MAX_ITEM_NAME + 3; + materials_column.left_margin = MAX_ITEM_NAME + MAX_MASK + 4; materials_column.title = "Material"; + populateItems(); - items_column.list[0].selected = true; + items_column.changeHighlight(0); vector raw_masks; df::dfhack_material_category full_mat_mask, curr_mat_mask; @@ -2145,6 +2227,10 @@ namespace wf_ui } populateMasks(cv != NULL); populateMaterials(cv != NULL); + + masks_column.selectDefaultEntry(); + materials_column.selectDefaultEntry(); + materials_column.changeHighlight(0); } void viewscreenChooseMaterial::populateItems() @@ -2154,12 +2240,50 @@ namespace wf_ui { items_column.add(cv->item.toString(), cv->item); } + else + { + typedef df::enum_traits traits; + int size = traits::last_item_value-traits::first_item_value+1; + for (size_t i = 0; i < size; i++) + { + //string item_name = traits::key_table[i]; + df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; + df::item_type val = df::item_type(i); + switch (val) + { +#define ITEM(type_string,vec,tclass) \ + case df::item_type::type_string: \ + for (size_t j = 0; j < defs.vec.size(); j++) \ + { \ + if (defs.vec[j]->name[0] == '?') \ + continue; \ + DFHack::ItemTypeInfo item; \ + item.type = (df::item_type) i; \ + item.subtype = j; \ + item.custom = defs.vec[j]; \ + items_column.add(defs.vec[j]->name, item); \ + } \ + break; + + ITEMDEF_VECTORS +#undef ITEM + default: + DFHack::ItemTypeInfo item; + item.type = (df::item_type) i; + string name = item.toString(); + if (name[0] != '?') + items_column.add(name, item); + break; + } + } + } + items_column.sort(); + items_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; @@ -2170,15 +2294,16 @@ namespace wf_ui } 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(); + vector selected_materials = masks_column.getSelectedElems(); if (selected_materials.size() == 1) - selected_category = selected_materials[0]; + selected_category = *selected_materials[0]; else if (selected_materials.size() > 1) return; @@ -2211,7 +2336,10 @@ namespace wf_ui material.decode(p->material_defs.type_basic_mat, p->material_defs.idx_basic_mat); if (!selected_category.whole || material.matches(selected_category)) { - ListEntry entry(pad_string(basename+" (all)", MAX_MATERIAL, false), material); + if (p->material.size() > 1) + basename.append(" (all)"); + + ListEntry entry(pad_string(basename, MAX_MATERIAL, false), material); if (set_defaults) { if (cv->material.matches(material)) @@ -2223,7 +2351,8 @@ namespace wf_ui { MaterialInfo material; material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); - if (addMaterialEntry(selected_category, material, basename+" (" + p->material[j]->id + "): " + material.toString(), set_defaults)) + //if (addMaterialEntry(selected_category, material, basename+" (" + p->material[j]->id + "): " + material.toString(), set_defaults)) + if (addMaterialEntry(selected_category, material, material.toString(), set_defaults)) entry.selected = false; } } @@ -2238,9 +2367,12 @@ namespace wf_ui { MaterialInfo material; material.decode(DFHack::MaterialInfo::CREATURE_BASE+j, i); - addMaterialEntry(selected_category, material, basename+" (" + p->material[j]->id + "): " + material.toString(), set_defaults); + //addMaterialEntry(selected_category, material, basename+" (" + p->material[j]->id + "): " + material.toString(), set_defaults); + addMaterialEntry(selected_category, material, material.toString(), set_defaults); } } + + materials_column.sort(); } @@ -2257,6 +2389,8 @@ namespace wf_ui { entry.selected = true; selected = true; + /*if (materials_column.highlighted_index == 0) + materials_column.highlighted_index = materials_column.getBaseListSize();*/ } } materials_column.add(entry); @@ -2267,39 +2401,78 @@ namespace wf_ui void viewscreenChooseMaterial::feed(set *input) { + bool key_processed; + switch (selected_column) + { + case 0: + key_processed = items_column.feed(input); + break; + case 1: + key_processed = masks_column.feed(input); + if (input->count(interface_key::SELECT)) + populateMaterials(false); + break; + case 2: + 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::CURSOR_UP)) + else if (input->count(interface_key::SEC_SELECT)) { - changeHighlight(-1); - } - else if (input->count(interface_key::CURSOR_DOWN)) - { - changeHighlight(1); + 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(); + + if (cv == NULL) + { + ItemConstraint *nct = create_new_constraint(false, *items_column.getFirstSelectedElem(), material, mat_mask, item_quality::Ordinary, NULL, ""); + nct->setGoalByCount(false); + nct->setGoalCount(10); + nct->setGoalGap(1); + } + else + { + cv->mat_mask = mat_mask; + cv->material = (selected_material) ? *selected_material : MaterialInfo(); + } + + reset_list = true; + Screen::dismiss(this); } else if (input->count(interface_key::CURSOR_LEFT)) { - changeColumn(-1); + --selected_column; } else if (input->count(interface_key::CURSOR_RIGHT)) { - changeColumn(1); - } - else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) - { - changeHighlight(0, -1); + selected_column++; } - else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) + else if (enabler->tracking_on && enabler->mouse_lbut) { - changeHighlight(0, 1); - } - else if (input->count(interface_key::SELECT)) - { - toggleHighlighted(); + if (items_column.setHighlightByMouse()) + selected_column = 0; + else if (masks_column.setHighlightByMouse()) + selected_column = 1; + else if (materials_column.setHighlightByMouse()) + selected_column = 2; + + enabler->mouse_lbut = enabler->mouse_rbut = 0; } } @@ -2313,57 +2486,31 @@ namespace wf_ui Screen::clear(); Screen::drawBorder(" Workflow Material "); - int x = 2; - items_column.display(x, selected_column == 0); + items_column.display(selected_column == 0); + masks_column.display(selected_column == 1); + materials_column.display(selected_column == 2); - x += MAX_ITEM_NAME + 1; - masks_column.display(x, selected_column == 1); - - x += MAX_MASK + 1; - materials_column.display(x, selected_column == 2); - - } - - void viewscreenChooseMaterial::changeHighlight(const int highlight_change, const int offset_shift /* = 0 */) - { - switch (selected_column) - { - case 0: - items_column.changeHighlight(highlight_change, offset_shift); - break; - case 1: - masks_column.changeHighlight(highlight_change, offset_shift); - break; - case 2: - materials_column.changeHighlight(highlight_change, offset_shift); - break; - } + 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::changeColumn(const int amount) + void viewscreenChooseMaterial::validateColumn() { - selected_column += amount; set_to_limit(selected_column, 2); } - void viewscreenChooseMaterial::toggleHighlighted() + void viewscreenChooseMaterial::resize(int32_t x, int32_t y) { - switch (selected_column) - { - case 0: - items_column.toggleHighlighted(); - break; - case 1: - masks_column.toggleHighlighted(); - populateMaterials(false); - break; - case 2: - materials_column.toggleHighlighted(); - break; - } + dfhack_viewscreen::resize(x, y); + items_column.resize(); + masks_column.resize(); + materials_column.resize(); } - + /* * Inventory Monitor */ @@ -2372,21 +2519,49 @@ namespace wf_ui private: struct TableRow { - string texts[INV_MONITOR_COL_COUNT]; - int8_t colors[INV_MONITOR_COL_COUNT]; - vector< pair > history_plot; int32_t limit_y, gap_y; - ItemConstraint *cv; + UIColor value_color; + UIColor limit_color; + int severity; }; + class MonitorListColumn : public ListColumn + { + public: + bool order_by_severity; - public: - const static int column_widths[]; - const static string column_titles[]; - const static int title_row; + virtual void sort() + { + if (!order_by_severity) + ListColumn::sort(); + else + doSort(&compareSeverity); + } + + private: + void display_extras(const TableRow &row, int32_t &x, int32_t &y) const + { + string text; + text.append(int_to_string((row.cv->goalByCount()) ? row.cv->item_count : row.cv->item_amount)); + text = pad_string(text, 6); + OutputString(row.value_color, x, y, text); + text = (row.cv->goalByCount()) ? " S " : " I "; + OutputString(COLOR_GREY, x, y, text); + + text = int_to_string(row.cv->goalCount()); + OutputString(row.limit_color, x, y, text); + } + + static bool compareSeverity(ListEntry const& a, ListEntry const& b) + { + return a.elem.severity > b.elem.severity; + } + }; + + public: viewscreenInventoryMonitor(); void feed(set *input); @@ -2401,53 +2576,42 @@ namespace wf_ui init(); } - virtual void onConstraintChanged(); private: - vector rows; - - int bottom_controls_row; - int table_max_rows; - int table_start_offset; - int selected_row; - + MonitorListColumn rows; + int32_t bottom_controls_row; int32_t divider_x; int chart_width, chart_height; int32_t axis_y_end, axis_y_start, axis_x_start, axis_x_end; void init(); - void changeSelection(const int amount); - - static bool compareConstraints(TableRow const& a, TableRow const& b) - { - return a.texts[0].compare(b.texts[0]) < 0; - } - }; - - const int viewscreenInventoryMonitor::title_row = 2; + virtual void onConstraintChanged(); - const int viewscreenInventoryMonitor::column_widths[] = - {MAX_ITEM_NAME, 20, 21}; + virtual void onModeChanged(); - const string viewscreenInventoryMonitor::column_titles[] = - {"Item", "Material", "Stock / Limit"}; + }; viewscreenInventoryMonitor::viewscreenInventoryMonitor() { adjustment_ui_display_start = 2; - selected_row = 0; chart_width = SIDEBAR_WIDTH - 2; - + rows.order_by_severity = false; + + rows.multiselect = false; + rows.allow_null = false; + rows.auto_select = true; + rows.title = pad_string("Item", MAX_ITEM_NAME, false); + rows.title += " "; + rows.title += pad_string("Material", MAX_MATERIAL, false); + init(); } void viewscreenInventoryMonitor::init() { - bottom_controls_row = gps->dimy - 2; - table_max_rows = bottom_controls_row - 4; - table_start_offset = 0; + bottom_controls_row = gps->dimy - 3; divider_x = gps->dimx - SIDEBAR_WIDTH - 2; chart_height = min(SIDEBAR_WIDTH, gps->dimy - 20); @@ -2462,46 +2626,107 @@ namespace wf_ui TableRow row; row.cv = *it; - row.texts[0] = row.cv->item.toString(); - row.colors[0] = COLOR_UNSELECTED; - - row.texts[1] = get_constraint_material(row.cv); - row.colors[1] = COLOR_UNSELECTED; - - string text; - text.append(int_to_string((row.cv->goalByCount()) ? row.cv->item_count : row.cv->item_amount)); - text.append(" "); - text.append((row.cv->goalByCount()) ? "S " : "I "); - text = pad_string(text, 9); - text.append(int_to_string(row.cv->goalCount())); - row.texts[2] = text; - row.colors[2] = COLOR_UNSELECTED; - - if (max_history_days > 0) + if (row.cv->jobs.size() > 0) + { + ProtectedJob *pj = row.cv->jobs[0]; + if (!pj->isActuallyResumed() && pj->want_resumed) + row.limit_color = COLOR_YELLOW; + else + row.limit_color = COLOR_GREY; + } + else + row.limit_color = COLOR_RED; + + + int curr_value = (row.cv->goalByCount()) ? row.cv->item_count : row.cv->item_amount; + row.value_color = COLOR_GREY; + if (curr_value >= row.cv->goalCount() - row.cv->goalGap()) + row.value_color = COLOR_LIGHTGREEN; + else if (curr_value >= (row.cv->goalCount() - row.cv->goalGap() * 2)) + row.value_color = COLOR_GREEN; + else if (curr_value <= row.cv->goalGap()) + row.value_color = COLOR_LIGHTRED; + else if (curr_value <= (row.cv->goalGap() * 2)) + row.value_color = COLOR_RED; + + int max_val = row.cv->goalCount(); + float scale_y; + if (max_history_days > 0 && row.cv->history.size() > 0) { row.history_plot.clear(); - int max_val = *max_element(row.cv->history.begin(), row.cv->history.end()); - max_val = max(max_val, row.cv->goalCount()); - float scale_y = (float) chart_height / (float) max_val; + max_val = max(max_val, *max_element(row.cv->history.begin(), row.cv->history.end())); + scale_y = (float) chart_height / (float) max_val; float scale_x = (float) chart_width / (float) (max_history_days * 2); - row.limit_y = axis_y_end - (int) (scale_y * (float) row.cv->goalCount()); - row.gap_y = axis_y_end - (int) (scale_y * (float) (row.cv->goalCount() - row.cv->goalGap())); - + int sumX, sumY, sumXY, sumXX; + sumX = sumY = sumXY = sumXX = 0; + for (size_t i = 0; i < row.cv->history.size(); i++) { pair point(axis_x_start + (int) (scale_x * (float) i), axis_y_end - (int) (scale_y * (float) row.cv->history[i])); row.history_plot.push_back(point); + + if (row.value_color == COLOR_GREY) + { + sumX += i; + sumY += row.cv->history[i]; + sumXY += i * row.cv->history[i]; + sumXX += i*i; + } + } + + if (row.value_color == COLOR_GREY) + { + int count = row.cv->history.size(); + float slope = (float) (count * sumXY - sumX * sumY) / + (float) (count * sumXX - sumX * sumX); + + if (slope > 0.3f) + row.value_color = COLOR_GREEN; + else if (slope < -0.3f) + row.value_color = COLOR_RED; } + + } + else + { + scale_y = (float) chart_height / (float) max_val; } - rows.push_back(row); - } + row.limit_y = axis_y_end - (int) (scale_y * (float) row.cv->goalCount()); + row.gap_y = axis_y_end - (int) (scale_y * (float) (row.cv->goalCount() - row.cv->goalGap())); - sort(rows.begin(), rows.end(), &viewscreenInventoryMonitor::compareConstraints); - changeSelection(0); + string text = pad_string(row.cv->item.toString(), MAX_ITEM_NAME, false); + text += " "; + text += pad_string(get_constraint_material(row.cv), MAX_MATERIAL, false, true); + + switch (row.value_color) + { + case COLOR_LIGHTGREEN: + row.severity = 0; + break; + case COLOR_GREEN: + row.severity = 1; + break; + case COLOR_GREY: + row.severity = 2; + break; + case COLOR_RED: + row.severity = 3; + break; + case COLOR_LIGHTRED: + row.severity = 4; + break; + default: + break; + } + + rows.add(text, row); + } + rows.sort(); + rows.changeHighlight(0); } void viewscreenInventoryMonitor::onConstraintChanged() @@ -2509,11 +2734,27 @@ namespace wf_ui init(); } + void viewscreenInventoryMonitor::onModeChanged() + { + TableRow *row = rows.getFirstSelectedElem(); + if (!row) + return; + row->cv->history.clear(); + init(); + } + void viewscreenInventoryMonitor::render() { if (Screen::isDismissed(this)) return; + if (viewscreenChooseMaterial::reset_list) + { + viewscreenChooseMaterial::reset_list = false; + init(); + return; + } + dfhack_viewscreen::render(); Screen::clear(); @@ -2525,42 +2766,29 @@ namespace wf_ui paintTile(border, divider_x, y); } - int32_t x = 2; - int32_t y = title_row; - + int32_t x = MAX_ITEM_NAME + MAX_MATERIAL + 3; + int32_t y = 2; + paint_text(COLOR_TITLE, x, y, "Stock / Limit"); - for (int column = 0; column < INV_MONITOR_COL_COUNT; column++) - { - paint_text(COLOR_TITLE, x, y, column_titles[column]); - x += column_widths[column]; - } - - int last_row_able_to_display = table_start_offset + table_max_rows; - for (int row = table_start_offset; row < last_row_able_to_display && row < rows.size(); row++) - { - x = 2; - ++y; - for (int column = 0; column < INV_MONITOR_COL_COUNT; column++) - { - int8_t color = rows[row].colors[column]; - if (column < 2 && row == selected_row) - color = COLOR_SELECTED; - paint_text(color, x, y, rows[row].texts[column]); - x += column_widths[column]; - } - } + rows.display(true); y = bottom_controls_row; x = 2; + OutputHotkeyString(x, y, "Add", "a"); + x += 2; OutputHotkeyString(x, y, "Edit", "e"); + + x += 2; + OutputHotkeyString(x, y, (rows.order_by_severity) ? "Name Order" : "Severity Order", "o"); - if (rows.size() > 0) + if (rows.getDisplayListSize() > 0) { - TableRow row = rows[selected_row]; - AdjustmentScreen::render(row.cv); + TableRow *row = rows.getFirstSelectedElem(); + AdjustmentScreen::render(row->cv); if (max_history_days > 0) { + paint_text(COLOR_BROWN, axis_x_start, axis_y_start-2, " Stock History Chart"); Screen::Pen y_axis('\xB3', COLOR_BROWN); x = axis_x_start; for (y = axis_y_start; y <= axis_y_end; y++) @@ -2576,8 +2804,8 @@ namespace wf_ui for (x = axis_x_start; x <= axis_x_end; x++) { paintTile(x_axis, x, y); - paint_text(COLOR_LIGHTGREEN, x, row.limit_y, "-"); - paint_text(COLOR_GREEN, x, row.gap_y, "-"); + paint_text(COLOR_LIGHTGREEN, x, row->limit_y, "-"); + paint_text(COLOR_GREEN, x, row->gap_y, "-"); } Screen::Pen right_arrow('\xAF', COLOR_BROWN); @@ -2586,10 +2814,10 @@ namespace wf_ui Screen::Pen zero_axis('\x9E', COLOR_BROWN); paintTile(zero_axis, axis_x_start, axis_y_end); - for (size_t i = 0; i < row.history_plot.size(); i++) + for (size_t i = 0; i < row->history_plot.size(); i++) { - int x = row.history_plot[i].first; - int y = row.history_plot[i].second; + int x = row->history_plot[i].first; + int y = row->history_plot[i].second; paint_text(COLOR_CYAN, x, y, "*"); } } @@ -2597,10 +2825,13 @@ namespace wf_ui } - + void viewscreenInventoryMonitor::feed(set *input) { - if (rows.size() > 0 && AdjustmentScreen::feed(input, rows[selected_row].cv)) + if (!rows.search_entry_mode && rows.getDisplayListSize() > 0 && AdjustmentScreen::feed(input, rows.getFirstSelectedElem()->cv)) + return; + + if (rows.feed(input)) return; if (input->count(interface_key::LEAVESCREEN)) @@ -2609,41 +2840,29 @@ namespace wf_ui Screen::dismiss(this); return; } - else if (input->count(interface_key::CURSOR_UP)) + else if (input->count(interface_key::CUSTOM_O)) { - changeSelection(-1); + rows.order_by_severity = !rows.order_by_severity; + rows.sort(); } - else if (input->count(interface_key::CURSOR_DOWN)) + else if ((input->count(interface_key::CUSTOM_E) || input->count(interface_key::SELECT)) && rows.getDisplayListSize() > 0) { - changeSelection(1); - } - else if (input->count(interface_key::STANDARDSCROLL_PAGEUP)) - { - table_start_offset = max(table_start_offset-table_max_rows, 0); - changeSelection(-table_max_rows); + TableRow *row = rows.getFirstSelectedElem(); + if (row) + { + Screen::show(new viewscreenChooseMaterial(row->cv)); + } } - else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) + else if (input->count(interface_key::CUSTOM_A)) { - table_start_offset = min(table_start_offset+table_max_rows, (int)(rows.size())-table_max_rows); - changeSelection(table_max_rows); + Screen::show(new viewscreenChooseMaterial()); } - else if (input->count(interface_key::CUSTOM_E)) + else if (enabler->tracking_on) { - Screen::show(new viewscreenChooseMaterial(rows[selected_row].cv)); + enabler->mouse_lbut = enabler->mouse_rbut = 0; } } - void viewscreenInventoryMonitor::changeSelection(int amount) - { - selected_row += amount; - set_to_limit(selected_row, rows.size() - 1); - - if (selected_row < table_start_offset) - table_start_offset = selected_row; - else if (selected_row >= table_start_offset + table_max_rows) - table_start_offset = selected_row - table_max_rows + 1; - } - /****************************** * Hook for workshop view * ******************************/ @@ -2659,7 +2878,7 @@ namespace wf_ui bool checkJobSelection() { if (!enabled) - return NULL; + return false; if (!first_update_done) { @@ -2677,6 +2896,14 @@ namespace wf_ui return job != NULL; } + bool can_enable_plugin() + { + return (!enabled && + Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && + ui->main.mode == ui_sidebar_mode::QueryBuilding && + Gui::getSelectedWorkshopJob(console_out, true)); + } + bool handleInput(set *input) { bool key_processed = true; @@ -2697,15 +2924,22 @@ namespace wf_ui if (!cv) { // Add tracking - job->flags.bits.repeat = true; compute_job_outputs(console_out, pj, true); - cv = pj->constraints[0]; - cv->setGoalByCount(false); - cv->setGoalCount(10); - cv->setGoalGap(1); + if (pj->constraints.size() > 0) + { + cv = pj->constraints[0]; + cv->setGoalByCount(false); + cv->setGoalCount(10); + cv->setGoalGap(1); + job->flags.bits.repeat = true; + } + else + { + Gui::showAnnouncement("Job type not currently supported", 6, true); + } } } - else if (input->count(interface_key::CUSTOM_I)) + else if (input->count(interface_key::CUSTOM_M)) { Screen::show(new viewscreenInventoryMonitor()); } @@ -2713,6 +2947,11 @@ namespace wf_ui key_processed = false; } } + else if (can_enable_plugin() && input->count(interface_key::CUSTOM_W)) + { + enable_plugin(console_out); + plugin_onupdate(console_out); + } else key_processed = false; @@ -2745,8 +2984,15 @@ namespace wf_ui cv = pj->constraints[0]; } dialog.render(cv); - ++dialog.y; - OutputHotkeyString(dialog.left_margin, dialog.y, "Inventory Monitor", "i"); + if (cv) + ++dialog.y; + OutputHotkeyString(dialog.left_margin, dialog.y, "Inventory Monitor", "m"); + } + else if (can_enable_plugin()) + { + int x = gps->dimx - 30; + int y = 24; + OutputHotkeyString(x, y, "Enable Workflow", "w"); } } }; @@ -2764,6 +3010,210 @@ namespace wf_ui #undef INV_MONITOR_COL_COUNT #undef MAX_ITEM_NAME + +static command_result workflow_cmd(color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("World is not loaded: please load a game first.\n"); + return CR_FAILURE; + } + + if (enabled) { + check_lost_jobs(out, 0); + recover_jobs(out); + update_job_data(out); + map_job_constraints(out); + map_job_items(out); + } + + df::building *workshop = NULL; + //FIXME: unused variable! + //df::job *job = NULL; + + if (Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && + ui->main.mode == ui_sidebar_mode::QueryBuilding) + { + workshop = world->selected_building; + //job = Gui::getSelectedWorkshopJob(out, true); + } + + std::string cmd = parameters.empty() ? "list" : parameters[0]; + + if (cmd == "enable" || cmd == "disable") + { + bool enable = (cmd == "enable"); + if (enable && !enabled) + { + enable_plugin(out); + } + else if (!enable && parameters.size() == 1) + { + if (enabled) + { + enabled = false; + setOptionEnabled(CF_ENABLED, false); + stop_protect(out); + } + + out << "The plugin is disabled." << endl; + return CR_OK; + } + + for (size_t i = 1; i < parameters.size(); i++) + { + if (parameters[i] == "drybuckets") + setOptionEnabled(CF_DRYBUCKETS, enable); + else if (parameters[i] == "auto-melt") + setOptionEnabled(CF_AUTOMELT, enable); + else + return CR_WRONG_USAGE; + } + + if (enabled) + out << "The plugin is enabled." << endl; + else + out << "The plugin is disabled." << endl; + + if (isOptionEnabled(CF_DRYBUCKETS)) + out << "Option drybuckets is enabled." << endl; + if (isOptionEnabled(CF_AUTOMELT)) + out << "Option auto-melt is enabled." << endl; + + return CR_OK; + } + else if (cmd == "count" || cmd == "amount") + { + if (!enabled) + enable_plugin(out); + } + + if (!enabled) + out << "Note: the plugin is not enabled." << endl; + + if (cmd == "jobs") + { + if (workshop) + { + for (size_t i = 0; i < workshop->jobs.size(); i++) + print_job(out, get_known(workshop->jobs[i]->id)); + } + else + { + for (TKnownJobs::iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) + if (it->second->isLive()) + print_job(out, it->second); + } + + bool pending = false; + + for (size_t i = 0; i < pending_recover.size(); i++) + { + if (!workshop || pending_recover[i]->holder == workshop) + { + if (!pending) + { + out.print("\nPending recovery:\n"); + pending = true; + } + + Job::printJobDetails(out, pending_recover[i]->job_copy); + } + } + + return CR_OK; + } + else if (cmd == "list") + { + for (size_t i = 0; i < constraints.size(); i++) + print_constraint(out, constraints[i]); + + return CR_OK; + } + else if (cmd == "list-commands") + { + for (size_t i = 0; i < constraints.size(); i++) + { + auto cv = constraints[i]; + out << "workflow " << (cv->goalByCount() ? "count " : "amount ") + << cv->config.val() << " " << cv->goalCount() << " " << cv->goalGap() << endl; + } + + return CR_OK; + } + else if (cmd == "count" || cmd == "amount") + { + if (parameters.size() < 3) + return CR_WRONG_USAGE; + + int limit = atoi(parameters[2].c_str()); + if (limit <= 0) { + out.printerr("Invalid limit value.\n"); + return CR_FAILURE; + } + + ItemConstraint *icv = get_constraint(out, parameters[1]); + if (!icv) + return CR_FAILURE; + + icv->setGoalByCount(cmd == "count"); + icv->setGoalCount(limit); + if (parameters.size() >= 4) + icv->setGoalGap(atoi(parameters[3].c_str())); + else + icv->setGoalGap(-1); + + process_constraints(out); + print_constraint(out, icv); + return CR_OK; + } + else if (cmd == "unlimit") + { + if (parameters.size() != 2) + return CR_WRONG_USAGE; + + for (size_t i = 0; i < constraints.size(); i++) + { + if (constraints[i]->config.val() != parameters[1]) + continue; + + delete_constraint(constraints[i]); + //process_constraints(out); + return CR_OK; + } + + out.printerr("Constraint not found: %s\n", parameters[1].c_str()); + return CR_FAILURE; + } + else if (cmd == "unlimit-all") + { + if (parameters.size() != 1) + return CR_WRONG_USAGE; + + while (!constraints.empty()) + delete_constraint(constraints[0]); + + //process_constraints(out); + + out.print("Removed all constraints.\n"); + return CR_OK; + } + else if (cmd == "monitor") + { + if (!enabled) + { + enable_plugin(out); + plugin_onupdate(out); + } + + Screen::show(new wf_ui::viewscreenInventoryMonitor()); + return CR_OK; + } + else + return CR_WRONG_USAGE; +} + DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (!world || !ui) From 0222b93bb0ba135fb7c36909d723992d88e2dc96 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 10 Nov 2012 03:47:15 +1300 Subject: [PATCH 003/138] Fix bug with non-repeating jobs getting protected --- plugins/workflow.cpp | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 0cf3b1c8f..0541e7670 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -640,7 +640,7 @@ static string get_constraint_material(ItemConstraint *cv) text.append(" "); } - text.append(bitfield_to_string(cv->mat_mask)); + text.append(bitfield_to_string(cv->mat_mask, ",")); return text; } @@ -2904,17 +2904,26 @@ namespace wf_ui Gui::getSelectedWorkshopJob(console_out, true)); } + ProtectedJob *get_protected_job(int32_t id) + { + ProtectedJob *pj = get_known(id); + if (!pj && job->flags.bits.repeat) + { + pj = add_known_job(job); + compute_job_outputs(console_out, pj); + } + + return pj; + } + bool handleInput(set *input) { bool key_processed = true; if (checkJobSelection()) { - ProtectedJob *pj = get_known(job->id); - if (!pj) - return false; - + ProtectedJob *pj = get_protected_job(job->id); ItemConstraint *cv = NULL; - if (pj->constraints.size() > 0) + if (pj && pj->constraints.size() > 0) cv = pj->constraints[0]; if (!dialog.feed(input, cv, pj)) @@ -2924,13 +2933,19 @@ namespace wf_ui if (!cv) { // Add tracking + if (!pj) + pj = add_known_job(job); + compute_job_outputs(console_out, pj, true); if (pj->constraints.size() > 0) { cv = pj->constraints[0]; - cv->setGoalByCount(false); - cv->setGoalCount(10); - cv->setGoalGap(1); + if (cv->goalCount() == -1) + { + cv->setGoalByCount(false); + cv->setGoalCount(10); + cv->setGoalGap(1); + } job->flags.bits.repeat = true; } else @@ -2971,18 +2986,13 @@ namespace wf_ui INTERPOSE_NEXT(render)(); if (checkJobSelection()) { - ProtectedJob *pj = get_known(job->id); - if (!pj) - { - pj = add_known_job(job); - compute_job_outputs(console_out, pj); - } - ItemConstraint *cv = NULL; - if (pj->constraints.size() > 0) + ProtectedJob *pj = get_protected_job(job->id); + if (pj && pj->constraints.size() > 0) { cv = pj->constraints[0]; } + dialog.render(cv); if (cv) ++dialog.y; From b16bbdd6df53cc598d608c893114df5bf45faed6 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 11 Nov 2012 03:00:55 +1300 Subject: [PATCH 004/138] Generate correct constraint string for persistence --- plugins/workflow.cpp | 87 ++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 0541e7670..e14398f0d 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -645,6 +645,23 @@ static string get_constraint_material(ItemConstraint *cv) return text; } +#define ITEMDEF_VECTORS \ + ITEM(WEAPON, weapons, itemdef_weaponst) \ + ITEM(TRAPCOMP, trapcomps, itemdef_trapcompst) \ + ITEM(TOY, toys, itemdef_toyst) \ + ITEM(TOOL, tools, itemdef_toolst) \ + ITEM(INSTRUMENT, instruments, itemdef_instrumentst) \ + ITEM(ARMOR, armor, itemdef_armorst) \ + ITEM(AMMO, ammo, itemdef_ammost) \ + ITEM(SIEGEAMMO, siege_ammo, itemdef_siegeammost) \ + ITEM(GLOVES, gloves, itemdef_glovesst) \ + ITEM(SHOES, shoes, itemdef_shoesst) \ + ITEM(SHIELD, shields, itemdef_shieldst) \ + ITEM(HELM, helms, itemdef_helmst) \ + ITEM(PANTS, pants, itemdef_pantsst) \ + ITEM(FOOD, food, itemdef_foodst) + + static ItemConstraint * create_new_constraint(bool is_craft, ItemTypeInfo item, MaterialInfo material, df::dfhack_material_category mat_mask, item_quality::item_quality minqual, PersistentDataItem * cfg, std::string str) @@ -681,8 +698,32 @@ static ItemConstraint * create_new_constraint(bool is_craft, ItemTypeInfo item, { if (str.empty()) { - str = get_constraint_material(nct); - str.append(item.toString()); + std::string item_token = ENUM_KEY_STR(item_type, item.type); + if (item.custom) + item_token += ":" + item.custom->id; + else if (item.subtype != -1) + { + df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; + switch (item.type) + { +#define ITEM(type_string,vec,tclass) \ + case df::item_type::type_string: \ + item_token += ":" + defs.vec[item.subtype]->id; \ + break; + + ITEMDEF_VECTORS +#undef ITEM + default: + break; + } + } + + str.append(item_token); + str.append("/"); + str.append(bitfield_to_string(mat_mask, ",")); + str.append("/"); + if ((material.type != 0 || material.index > 0) && !material.isNone()) + str.append(material.getToken()); } nct->config = Core::getInstance().getWorld()->AddPersistentData("workflow/constraints"); @@ -847,7 +888,7 @@ static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t i } } -static void compute_custom_job(ProtectedJob *pj, df::job *job) +static void compute_custom_job(ProtectedJob *pj, df::job *job, bool create_constraint = false) { if (pj->reaction_id < 0) pj->reaction_id = linear_index(df::reaction::get_vector(), @@ -908,7 +949,7 @@ static void compute_custom_job(ProtectedJob *pj, df::job *job) } link_job_constraint(pj, prod->item_type, prod->item_subtype, - mat_mask, mat.type, mat.index, prod->flags.is_set(CRAFTS)); + mat_mask, mat.type, mat.index, prod->flags.is_set(CRAFTS), create_constraint); } } @@ -962,7 +1003,7 @@ static void compute_job_outputs(color_ostream &out, ProtectedJob *pj, bool creat if (job->job_type == CustomReaction) { - compute_custom_job(pj, job); + compute_custom_job(pj, job, create_constraint); return; } @@ -1534,22 +1575,6 @@ static void print_job(color_ostream &out, ProtectedJob *pj) #define COLOR_SELECTED COLOR_WHITE #define COLOR_HIGHLIGHTED COLOR_GREEN -#define ITEMDEF_VECTORS \ - ITEM(WEAPON, weapons, itemdef_weaponst) \ - ITEM(TRAPCOMP, trapcomps, itemdef_trapcompst) \ - ITEM(TOY, toys, itemdef_toyst) \ - ITEM(TOOL, tools, itemdef_toolst) \ - ITEM(INSTRUMENT, instruments, itemdef_instrumentst) \ - ITEM(ARMOR, armor, itemdef_armorst) \ - ITEM(AMMO, ammo, itemdef_ammost) \ - ITEM(SIEGEAMMO, siege_ammo, itemdef_siegeammost) \ - ITEM(GLOVES, gloves, itemdef_glovesst) \ - ITEM(SHOES, shoes, itemdef_shoesst) \ - ITEM(SHIELD, shields, itemdef_shieldst) \ - ITEM(HELM, helms, itemdef_helmst) \ - ITEM(PANTS, pants, itemdef_pantsst) \ - ITEM(FOOD, food, itemdef_foodst) - namespace wf_ui { /* @@ -2439,18 +2464,21 @@ namespace wf_ui MaterialInfo *selected_material = materials_column.getFirstSelectedElem(); MaterialInfo material = (selected_material) ? *selected_material : MaterialInfo(); - if (cv == NULL) + if (cv != NULL) { - ItemConstraint *nct = create_new_constraint(false, *items_column.getFirstSelectedElem(), material, mat_mask, item_quality::Ordinary, NULL, ""); - nct->setGoalByCount(false); - nct->setGoalCount(10); - nct->setGoalGap(1); + delete_constraint(cv); } - else + + ItemConstraint *nct = create_new_constraint(false, *items_column.getFirstSelectedElem(), material, mat_mask, item_quality::Ordinary, NULL, ""); + nct->setGoalByCount(false); + nct->setGoalCount(10); + nct->setGoalGap(1); + + /*else { cv->mat_mask = mat_mask; cv->material = (selected_material) ? *selected_material : MaterialInfo(); - } + }*/ reset_list = true; Screen::dismiss(this); @@ -3189,7 +3217,6 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet continue; delete_constraint(constraints[i]); - //process_constraints(out); return CR_OK; } @@ -3204,8 +3231,6 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet while (!constraints.empty()) delete_constraint(constraints[0]); - //process_constraints(out); - out.print("Removed all constraints.\n"); return CR_OK; } From 6803a83e0773b81fd193dd964b19050544483393 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 11 Nov 2012 18:18:54 +1300 Subject: [PATCH 005/138] Refresh constraints on map reload --- plugins/workflow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index e14398f0d..1a8f93a9c 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -406,6 +406,7 @@ static void start_protect(color_ostream &out) out.print("Protecting %d jobs.\n", known_jobs.size()); } +static bool first_update_done = false; static void init_state(color_ostream &out) { auto pworld = Core::getInstance().getWorld(); @@ -430,6 +431,7 @@ static void init_state(color_ostream &out) last_tick_frame_count = world->frame_counter; last_frame_count = world->frame_counter; + first_update_done = false; if (!enabled) return; @@ -437,7 +439,6 @@ static void init_state(color_ostream &out) start_protect(out); } -static bool first_update_done = false; static void enable_plugin(color_ostream &out) { auto pworld = Core::getInstance().getWorld(); From 7ee817b324bdadc7cfc1c5cf86ad0629e92cb25a Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 11 Nov 2012 19:42:30 +1300 Subject: [PATCH 006/138] Add gcc compile fixes --- plugins/workflow.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 1a8f93a9c..2d3746e71 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -466,7 +466,7 @@ static void enable_plugin(color_ostream &out) * JOB AUTO-RECOVERY * ******************************/ -static void forget_job(color_ostream &out, ProtectedJob *pj) +static void forget_job(const color_ostream &out, ProtectedJob *pj) { known_jobs.erase(pj->id); delete pj; @@ -1880,7 +1880,7 @@ namespace wf_ui max_item_width = entry.text.length(); } - void add(string &text, T &elem) + void add(const string &text, T &elem) { list.push_back(ListEntry(text, elem)); if (text.length() > max_item_width) @@ -1990,7 +1990,7 @@ namespace wf_ui if (auto_select && !multiselect) { - for (vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) + for (typename vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) { it->selected = false; } @@ -2026,7 +2026,7 @@ namespace wf_ui vector getSelectedElems(bool only_one = false) { vector results; - for (vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) + for (typename vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) { if ((*it).selected) { From 0e2aa299b9881c1530aef55ed5880bc846297815 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Wed, 14 Nov 2012 20:26:18 +1300 Subject: [PATCH 007/138] Auto material selection plugin - First push --- plugins/CMakeLists.txt | 1 + plugins/automaterial.cpp | 422 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 plugins/automaterial.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index fc3d0743e..34fedd77e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -118,6 +118,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(search search.cpp) + DFHACK_PLUGIN(automaterial automaterial.cpp) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp new file mode 100644 index 000000000..186815d36 --- /dev/null +++ b/plugins/automaterial.cpp @@ -0,0 +1,422 @@ +// Auto Material Select + +#include +#include +#include + +#include "Core.h" +#include +#include +#include +#include + + +// DF data structure definition headers +#include "DataDefs.h" +#include "MiscUtils.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/ui_build_selector.h" +#include "df/build_req_choice_genst.h" +#include "df/build_req_choice_specst.h" +#include "df/item.h" + +#include "df/ui.h" +#include "modules/Gui.h" +#include "modules/Screen.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; + + +DFHACK_PLUGIN("automaterial"); + +struct MaterialDescriptor +{ + int16_t type; + int32_t index; + bool valid; + + bool matches(const MaterialDescriptor &a) const + { + return a.valid && valid && a.type == type && a.index == index; + } +}; + +typedef int16_t construction_type; + +static map last_used_material; +static map last_moved_material; +static map< construction_type, vector > preferred_materials; +static map< construction_type, df::interface_key > hotkeys; +static bool last_used_moved = false; +static bool auto_choose_materials = true; +static bool auto_choose_attempted = true; +static bool revert_to_last_used_type = false; + +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); +} + + +static inline bool in_material_choice_stage() +{ + return Gui::build_selector_hotkey(Core::getTopViewscreen()) && + ui->main.mode == ui_sidebar_mode::Build && + ui_build_selector->stage == 2; +} + +static inline bool in_type_choice_stage() +{ + return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && + ui->main.mode == ui_sidebar_mode::Build && + ui_build_selector && + ui_build_selector->building_type >= 0 && + ui_build_selector->stage == 1; +} + +static inline vector &get_curr_constr_prefs() +{ + if (preferred_materials.find(ui_build_selector->building_subtype) == preferred_materials.end()) + preferred_materials[ui_build_selector->building_subtype] = vector(); + + return preferred_materials[ui_build_selector->building_subtype]; +} + +static inline MaterialDescriptor &get_last_used_material() +{ + if (last_used_material.find(ui_build_selector->building_subtype) == last_used_material.end()) + last_used_material[ui_build_selector->building_subtype] = MaterialDescriptor(); + + return last_used_material[ui_build_selector->building_subtype]; +} + +static void set_last_used_material(MaterialDescriptor &matetial) +{ + last_used_material[ui_build_selector->building_subtype] = matetial; +} + +static MaterialDescriptor &get_last_moved_material() +{ + if (last_moved_material.find(ui_build_selector->building_subtype) == last_moved_material.end()) + last_moved_material[ui_build_selector->building_subtype] = MaterialDescriptor(); + + return last_moved_material[ui_build_selector->building_subtype]; +} + +static void set_last_moved_material(MaterialDescriptor &matetial) +{ + last_moved_material[ui_build_selector->building_subtype] = matetial; +} + +static MaterialDescriptor get_material_in_list(size_t i) +{ + MaterialDescriptor result; + result.valid = false; + + if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, ui_build_selector->choices[i])) + { + result.type = gen->mat_type; + result.index = gen->mat_index; + result.valid = true; + } + else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) + { + result.type = spec->candidate->getActualMaterial(); + result.index = spec->candidate->getActualMaterialIndex(); + result.valid = true; + } + + return result; +} + + +static bool is_material_in_autoselect(size_t &i, MaterialDescriptor &material) +{ + for (i = 0; i < get_curr_constr_prefs().size(); i++) + { + if (get_curr_constr_prefs()[i].matches(material)) + return true; + } + + return false; +} + +static bool is_material_in_list(size_t &i, MaterialDescriptor &material) +{ + const size_t size = ui_build_selector->choices.size(); //Just because material list could be very big + for (i = 0; i < size; i++) + { + if (get_material_in_list(i).matches(material)) + return true; + } + + return false; +} + +static bool move_material_to_top(MaterialDescriptor &material) +{ + size_t i; + if (is_material_in_list(i, material)) + { + auto sel_item = ui_build_selector->choices[i]; + ui_build_selector->choices.erase(ui_build_selector->choices.begin() + i); + ui_build_selector->choices.insert(ui_build_selector->choices.begin(), sel_item); + + ui_build_selector->sel_index = 0; + set_last_moved_material(material); + return true; + } + + set_last_moved_material(MaterialDescriptor()); + return false; +} + +static bool choose_materials() +{ + size_t size = ui_build_selector->choices.size(); + for (size_t i = 0; i < size; i++) + { + MaterialDescriptor material = get_material_in_list(i); + size_t j; + if (is_material_in_autoselect(j, material)) + { + ui_build_selector->sel_index = i; + std::set< df::interface_key > keys; + keys.insert(df::interface_key::SELECT_ALL); + Core::getTopViewscreen()->feed(&keys); + if (!in_material_choice_stage()) + return true; + } + } + + return false; +} + +static bool check_autoselect(MaterialDescriptor &material, bool toggle) +{ + size_t idx; + if (is_material_in_autoselect(idx, material)) + { + if (toggle) + vector_erase_at(get_curr_constr_prefs(), idx); + + return true; + } + else + { + if (toggle) + get_curr_constr_prefs().push_back(material); + + return false; + } +} + +struct jobutils_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + static color_ostream_proxy console_out; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (in_material_choice_stage()) + { + MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); + if (material.valid) + { + if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)) + { + if (get_last_moved_material().matches(material)) + last_used_moved = false; + + set_last_used_material(material); + } + else if (input->count(interface_key::CUSTOM_A)) + { + check_autoselect(material, true); + input->clear(); + } + } + } + else if (in_type_choice_stage()) + { + if (input->count(interface_key::CUSTOM_A)) + { + auto_choose_materials = !auto_choose_materials; + } + else if (input->count(interface_key::CUSTOM_T)) + { + revert_to_last_used_type = !revert_to_last_used_type; + } + } + + construction_type last_used_constr_subtype = (in_material_choice_stage()) ? ui_build_selector->building_subtype : -1; + INTERPOSE_NEXT(feed)(input); + + if (revert_to_last_used_type && last_used_constr_subtype >= 0 && !in_material_choice_stage()) + { + input->clear(); + std::set< df::interface_key > keys; + keys.insert(hotkeys[last_used_constr_subtype]); + Core::getTopViewscreen()->feed(&keys); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + if (in_material_choice_stage()) + { + if (!last_used_moved) + { + if (auto_choose_materials && get_curr_constr_prefs().size() > 0) + { + last_used_moved = true; + if (choose_materials()) + { + return; + } + } + else if (ui_build_selector->is_grouped) + { + last_used_moved = true; + move_material_to_top(get_last_used_material()); + } + } + else if (!ui_build_selector->is_grouped) + { + last_used_moved = false; + } + } + else + { + last_used_moved = false; + } + + INTERPOSE_NEXT(render)(); + + if (in_material_choice_stage()) + { + MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); + if (material.valid) + { + int left_margin = gps->dimx - 30; + int x = left_margin; + int y = 25; + + string toggle_string = "Enable"; + string title = "Disabled"; + if (check_autoselect(material, false)) + { + toggle_string = "Disable"; + title = "Enabled"; + } + + OutputString(COLOR_BROWN, x, y, "DFHack Autoselect: " + title, true, left_margin); + OutputHotkeyString(x, y, toggle_string.c_str(), "a", true, left_margin); + } + } + else if (in_type_choice_stage() && ui_build_selector->building_subtype != 7) + { + int left_margin = gps->dimx - 30; + int x = left_margin; + int y = 25; + + string autoselect_toggle_string = (auto_choose_materials) ? "Disable Auto Mat-select" : "Enable Auto Mat-select"; + string revert_toggle_string = (revert_to_last_used_type) ? "Disable Auto Type-select" : "Enable Auto Type-select"; + + OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); + OutputHotkeyString(x, y, autoselect_toggle_string.c_str(), "a", true, left_margin); + OutputHotkeyString(x, y, revert_toggle_string.c_str(), "t", true, left_margin); + } + } +}; + +color_ostream_proxy console_out(Core::getInstance().getConsole()); + + +IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (!gps || !INTERPOSE_HOOK(jobutils_hook, feed).apply() || !INTERPOSE_HOOK(jobutils_hook, render).apply()) + out.printerr("Could not insert jobutils hooks!\n"); + + hotkeys[1] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL; + hotkeys[2] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FLOOR; + hotkeys[6] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_RAMP; + hotkeys[3] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UP; + hotkeys[4] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_DOWN; + hotkeys[5] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UPDOWN; + hotkeys[0] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FORTIFICATION; + hotkeys[7] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_TRACK; + hotkeys[5] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_TRACK_STOP; + + commands.push_back(PluginCommand( + "automaterial", "Makes construction easier by auto selecting materials", + automaterial_cmd, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " Makes construction easier by auto selecting materials.\n" + )); + return CR_OK; +} + + +/* +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_GAME_LOADED: + // initialize from the world just loaded + break; + case SC_GAME_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} +*/ + +/* +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + return CR_OK; +} +*/ \ No newline at end of file From 5c18952caa9bc287d186e714a4a3ceb2a12928fc Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 20 Nov 2012 22:52:44 +1300 Subject: [PATCH 008/138] Select by item name --- plugins/automaterial.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 186815d36..dc86e9c3a 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -39,13 +39,19 @@ DFHACK_PLUGIN("automaterial"); 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; + return a.valid && valid && + a.type == type && + a.index == index && + a.item_type == item_type && + a.item_subtype == item_subtype; } }; @@ -150,12 +156,16 @@ static MaterialDescriptor get_material_in_list(size_t i) if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, ui_build_selector->choices[i])) { + result.item_type = gen->item_type; + result.item_subtype = gen->item_subtype; result.type = gen->mat_type; result.index = gen->mat_index; result.valid = true; } else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) { + result.item_type = gen->item_type; + result.item_subtype = gen->item_subtype; result.type = spec->candidate->getActualMaterial(); result.index = spec->candidate->getActualMaterialIndex(); result.valid = true; @@ -250,8 +260,6 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; - static color_ostream_proxy console_out; - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { if (in_material_choice_stage()) From e750cbe5e90aecb7a41c4259d6d611afb2f136dd Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 24 Nov 2012 19:48:26 +1300 Subject: [PATCH 009/138] MouseQuery Plugin --- plugins/CMakeLists.txt | 1 + plugins/mousequery.cpp | 264 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 plugins/mousequery.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..946c51181 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -117,6 +117,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(regrass regrass.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp) + DFHACK_PLUGIN(mousequery mousequery.cpp) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp new file mode 100644 index 000000000..21215ff54 --- /dev/null +++ b/plugins/mousequery.cpp @@ -0,0 +1,264 @@ +#include + +#include "Core.h" +#include +#include +#include +#include + +#include "DataDefs.h" + +#include "df/building.h" +#include "df/enabler.h" +#include "df/item.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" + + +using std::set; +using std::string; +using std::ostringstream; + +using namespace DFHack; +using namespace df::enums; + +using df::global::enabler; +using df::global::gps; +using df::global::world; +using df::global::ui; + + +static int32_t last_x, last_y, last_z; +static size_t max_list_size = 100000; // Avoid iterating over huge lists + +struct mousequery_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + void send_key(const df::interface_key &key) + { + set tmp; + tmp.insert(key); + //INTERPOSE_NEXT(feed)(&tmp); + this->feed(&tmp); + } + + df::interface_key get_default_query_mode(const int32_t &x, const int32_t &y, const int32_t &z) + { + bool fallback_to_building_query = false; + + // Check for unit under cursor + size_t count = world->units.all.size(); + if (count <= max_list_size) + { + for(size_t i = 0; i < count; i++) + { + df::unit *unit = world->units.all[i]; + + if(unit->pos.x == x && unit->pos.y == y && unit->pos.z == z) + return df::interface_key::D_VIEWUNIT; + } + } + else + { + fallback_to_building_query = true; + } + + // Check for building under cursor + count = world->buildings.all.size(); + if (count <= max_list_size) + { + for(size_t i = 0; i < count; i++) + { + df::building *bld = world->buildings.all[i]; + + if (z == bld->z && + x >= bld->x1 && x <= bld->x2 && + y >= bld->y1 && y <= bld->y2) + { + df::building_type type = bld->getType(); + + if (type == building_type::Stockpile) + { + fallback_to_building_query = true; + break; // Check for items in stockpile first + } + + // For containers use item view, fir everything else, query view + return (type == building_type::Box || type == building_type::Cabinet || + type == building_type::Weaponrack || type == building_type::Armorstand) + ? df::interface_key::D_BUILDITEM : df::interface_key::D_BUILDJOB; + } + } + } + else + { + fallback_to_building_query = true; + } + + + // Check for items under cursor + count = world->items.all.size(); + if (count <= max_list_size) + { + for(size_t i = 0; i < count; i++) + { + df::item *item = world->items.all[i]; + if (z == item->pos.z && x == item->pos.x && y == item->pos.y && + !item->flags.bits.in_building && !item->flags.bits.hidden && + !item->flags.bits.in_job && !item->flags.bits.in_chest && + !item->flags.bits.in_inventory) + { + return df::interface_key::D_LOOK; + } + } + } + else + { + fallback_to_building_query = true; + } + + return (fallback_to_building_query) ? df::interface_key::D_BUILDJOB : df::interface_key::D_LOOK; + } + + bool handle_mouse(const set *input) + { + int32_t cx, cy, vz; + if (enabler->tracking_on) + { + if (enabler->mouse_lbut) + { + int32_t mx, my; + if (Gui::getMousePos(mx, my)) + { + int32_t vx, vy; + if (Gui::getViewCoords(vx, vy, vz)) + { + cx = vx + mx - 1; + cy = vy + my - 1; + + using namespace df::enums::ui_sidebar_mode; + df::interface_key key = interface_key::NONE; + bool cursor_still_here = (last_x == cx && last_y == cy && last_z == vz); + switch(ui->main.mode) + { + case QueryBuilding: + if (cursor_still_here) + key = df::interface_key::D_BUILDITEM; + break; + + case BuildingItems: + if (cursor_still_here) + key = df::interface_key::D_VIEWUNIT; + break; + + case ViewUnits: + if (cursor_still_here) + key = df::interface_key::D_LOOK; + break; + + case LookAround: + if (cursor_still_here) + key = df::interface_key::D_BUILDJOB; + break; + + case Default: + break; + + default: + return false; + } + + enabler->mouse_lbut = 0; + + // Can't check limits earlier as we must be sure we are in query or default mode we can clear the button flag + // Otherwise the feed gets stuck in a loop + uint8_t menu_width, area_map_width; + Gui::getMenuWidth(menu_width, area_map_width); + int32_t w = gps->dimx; + if (menu_width == 1) w -= 57; //Menu is open doubly wide + else if (menu_width == 2 && area_map_width == 3) w -= 33; //Just the menu is open + else if (menu_width == 2 && area_map_width == 2) w -= 26; //Just the area map is open + + if (mx < 1 || mx > w || my < 1 || my > gps->dimy - 2) + return false; + + while (ui->main.mode != Default) + { + send_key(df::interface_key::LEAVESCREEN); + } + + if (key == interface_key::NONE) + key = get_default_query_mode(cx, cy, vz); + + send_key(key); + + // Force UI refresh + Gui::setCursorCoords(cx, cy, vz); + send_key(interface_key::CURSOR_DOWN_Z); + send_key(interface_key::CURSOR_UP_Z); + last_x = cx; + last_y = cy; + last_z = vz; + + return true; + } + } + } + else if (enabler->mouse_rbut) + { + // Escape out of query mode + using namespace df::enums::ui_sidebar_mode; + if (ui->main.mode == QueryBuilding || ui->main.mode == BuildingItems || + ui->main.mode == ViewUnits || ui->main.mode == LookAround) + { + while (ui->main.mode != Default) + { + send_key(df::interface_key::LEAVESCREEN); + } + + enabler->mouse_rbut = 0; + } + } + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handle_mouse(input)) + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(mousequery_hook, feed); + +DFHACK_PLUGIN("mousequery"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (!gps || !INTERPOSE_HOOK(mousequery_hook, feed).apply()) + out.printerr("Could not insert mousequery hooks!\n"); + + last_x = last_y = last_z = -1; + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + last_x = last_y = last_z = -1; + break; + default: + break; + } + return CR_OK; +} From 3db5684ae7aa3f79d780b848a9519e904b6ffff2 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 25 Nov 2012 19:01:58 +1300 Subject: [PATCH 010/138] Fix handling of manipulator hotkey in unit search screen --- plugins/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index fdc788955..ca2a1e9b1 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -482,7 +482,8 @@ private: virtual bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L)) + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + (!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) { if (!is_entry_mode()) { From 7a2a746347d8e75dc341e617a378bc71a4115f18 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 26 Nov 2012 22:03:23 +1300 Subject: [PATCH 011/138] Add filtering to zone creature assignment menu. First steps towards adding autopasture functionality. --- plugins/zone.cpp | 381 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 353 insertions(+), 28 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 8e73ba34b..e83e6f765 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -47,7 +47,9 @@ using namespace std; #include "modules/MapCache.h" #include "modules/Buildings.h" #include "modules/World.h" +#include "modules/Screen.h" #include "MiscUtils.h" +#include #include #include "df/world.h" @@ -60,6 +62,8 @@ using namespace std; #include "df/general_ref_building_civzone_assignedst.h" #include #include +#include "df/viewscreen_dwarfmodest.h" +#include "modules/Translation.h" using std::vector; using std::string; @@ -68,6 +72,8 @@ using namespace df::enums; using df::global::world; using df::global::cursor; using df::global::ui; +using df::global::ui_build_selector; +using df::global::gps; using namespace DFHack::Gui; @@ -238,34 +244,6 @@ command_result init_autonestbox(color_ostream &out); command_result cleanup_autonestbox(color_ostream &out); command_result start_autonestbox(color_ostream &out); -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "zone", "manage activity zones.", - df_zone, false, - zone_help.c_str() - )); - commands.push_back(PluginCommand( - "autonestbox", "auto-assign nestbox zones.", - df_autonestbox, false, - autonestbox_help.c_str() - )); - commands.push_back(PluginCommand( - "autobutcher", "auto-assign lifestock for butchering.", - df_autobutcher, false, - autobutcher_help.c_str() - )); - init_autobutcher(out); - init_autonestbox(out); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - cleanup_autobutcher(out); - cleanup_autonestbox(out); - return CR_OK; -} /////////////// // stuff for autonestbox and autobutcher @@ -1024,6 +1002,22 @@ bool isAssigned(df::unit* unit) return assigned; } +bool isAssignedToZone(df::unit* unit) +{ + bool assigned = false; + for (size_t r=0; r < unit->refs.size(); r++) + { + df::general_ref * ref = unit->refs[r]; + auto rtype = ref->getType(); + if(rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) + { + assigned = true; + break; + } + } + return assigned; +} + // check if assigned to a chain or built cage // (need to check if the ref needs to be removed, until then touching them is forbidden) bool isChained(df::unit* unit) @@ -3545,3 +3539,334 @@ command_result cleanup_autonestbox(color_ostream &out) // (future version of autonestbox could store info about cages for useless male kids) return CR_OK; } + + +//START zone filters +using df::global::ui_building_item_cursor; +using df::global::ui_building_assign_type; +using df::global::ui_building_assign_is_marked; +using df::global::ui_building_assign_units; +using df::global::ui_building_assign_items; + +static const int ascii_to_enum_offset = interface_key::STRING_A048 - '0'; + +void OutputString(int8_t color, int &x, int y, const std::string &text) +{ + Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); + x += text.length(); +} + +class zone_filter +{ +public: + zone_filter() + { + initialized = false; + } + + void initialize(const df::ui_sidebar_mode &mode) + { + if (!initialized) + { + this->mode = mode; + saved_ui_building_assign_type.clear(); + saved_ui_building_assign_units.clear(); + saved_ui_building_assign_items.clear(); + saved_ui_building_assign_is_marked.clear(); + saved_indexes.clear(); + + for (size_t i = 0; i < ui_building_assign_units->size(); i++) + { + saved_ui_building_assign_type.push_back(ui_building_assign_type->at(i)); + saved_ui_building_assign_units.push_back(ui_building_assign_units->at(i)); + saved_ui_building_assign_items.push_back(ui_building_assign_items->at(i)); + saved_ui_building_assign_is_marked.push_back(ui_building_assign_is_marked->at(i)); + } + + search_string.clear(); + show_non_grazers = show_pastured = show_other_zones = true; + entry_mode = false; + + initialized = true; + } + } + + void deinitialize() + { + initialized = false; + } + + void apply_filters() + { + if (saved_indexes.size() > 0) + { + bool list_has_been_sorted = (ui_building_assign_units->size() == reference_list.size() + && *ui_building_assign_units != reference_list); + + for (size_t i = 0; i < saved_indexes.size(); i++) + { + int adjusted_item_index = i; + if (list_has_been_sorted) + { + for (int j = 0; j < ui_building_assign_units->size(); j++) + { + if (ui_building_assign_units->at(j) == reference_list[i]) + { + adjusted_item_index = j; + break; + } + } + } + + saved_ui_building_assign_is_marked[saved_indexes[i]] = ui_building_assign_is_marked->at(adjusted_item_index); + } + } + + string search_string_l = toLower(search_string); + saved_indexes.clear(); + ui_building_assign_type->clear(); + ui_building_assign_is_marked->clear(); + ui_building_assign_units->clear(); + ui_building_assign_items->clear(); + + for (size_t i = 0; i < saved_ui_building_assign_units.size(); i++) + { + df::unit *curr_unit = saved_ui_building_assign_units[i]; + + if (!curr_unit) + continue; + + if (!show_non_grazers && !isGrazer(curr_unit)) + continue; + + if (!show_pastured && isAssignedToZone(curr_unit)) + continue; + + if (!search_string_l.empty()) + { + string desc = Translation::TranslateName( + Units::getVisibleName(curr_unit), false); + + desc += Units::getProfessionName(curr_unit); + desc = toLower(desc); + + if (desc.find(search_string_l) == string::npos) + continue; + } + + ui_building_assign_type->push_back(saved_ui_building_assign_type[i]); + ui_building_assign_units->push_back(curr_unit); + ui_building_assign_items->push_back(saved_ui_building_assign_items[i]); + ui_building_assign_is_marked->push_back(saved_ui_building_assign_is_marked[i]); + + saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed + } + + reference_list = *ui_building_assign_units; + *ui_building_item_cursor = 0; + } + + bool handle_input(const set *input) + { + if (!initialized) + return false; + + bool key_processed = true; + + if (entry_mode) + { + // Query typing mode + + if (input->count(interface_key::SECONDSCROLL_UP) || input->count(interface_key::SECONDSCROLL_DOWN) || + input->count(interface_key::SECONDSCROLL_PAGEUP) || input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + // Arrow key pressed. Leave entry mode and allow screen to process key + entry_mode = false; + return false; + } + + 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; + apply_filters(); + } + else if (last_token == interface_key::STRING_A000) + { + // Backspace + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + apply_filters(); + } + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + // ENTER or ESC: leave typing mode + entry_mode = false; + } + } + // Not in query typing mode + else if (input->count(interface_key::CUSTOM_G) && mode == ui_sidebar_mode::ZonesPenInfo) + { + show_non_grazers = !show_non_grazers; + apply_filters(); + } + else if (input->count(interface_key::CUSTOM_P) && mode == ui_sidebar_mode::ZonesPenInfo) + { + show_pastured = !show_pastured; + apply_filters(); + } + else if (input->count(interface_key::CUSTOM_S)) + { + // Hotkey pressed, enter typing mode + entry_mode = true; + } + else if (input->count(interface_key::CUSTOM_SHIFT_S)) + { + // Shift + Hotkey pressed, clear query + search_string.clear(); + apply_filters(); + } + else + { + // Not a key for us, pass it on to the screen + key_processed = false; + } + + return key_processed || entry_mode; // Only pass unrecognized keys down if not in typing mode + } + + void do_render() + { + if (!initialized) + return; + + int left_margin = gps->dimx - 30; + int8_t a = *df::global::ui_menu_width; + int8_t b = *df::global::ui_area_map_width; + if ((a == 1 && b > 1) || (a == 2 && b == 2)) + left_margin -= 24; + + int x = left_margin; + int y = 24; + + OutputString(COLOR_BROWN, x, y, "DFHack Filtering"); + x = left_margin; + ++y; + OutputString(COLOR_LIGHTGREEN, x, y, "s"); + OutputString(COLOR_WHITE, x, y, ": Search"); + if (!search_string.empty() || entry_mode) + { + OutputString(COLOR_WHITE, x, y, ": "); + if (!search_string.empty()) + OutputString(COLOR_WHITE, x, y, search_string); + if (entry_mode) + OutputString(COLOR_LIGHTGREEN, x, y, "_"); + } + + if (mode == ui_sidebar_mode::ZonesPenInfo) + { + x = left_margin; + y += 2; + OutputString(COLOR_LIGHTGREEN, x, y, "g"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_non_grazers) ? COLOR_WHITE : COLOR_GREY, x, y, "Non-Grazing"); + + x = left_margin; + ++y; + OutputString(COLOR_LIGHTGREEN, x, y, "p"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_pastured) ? COLOR_WHITE : COLOR_GREY, x, y, "Currently Pastured"); + } + } + +private: + df::ui_sidebar_mode mode; + string search_string; + bool initialized; + bool entry_mode; + bool show_non_grazers, show_pastured, show_other_zones; + + std::vector saved_ui_building_assign_type; + std::vector saved_ui_building_assign_units, reference_list; + std::vector saved_ui_building_assign_items; + std::vector saved_ui_building_assign_is_marked; + + vector saved_indexes; + +}; + +struct zone_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + static zone_filter filter; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!filter.handle_input(input)) + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + if ((ui->main.mode == ui_sidebar_mode::ZonesPenInfo || ui->main.mode == ui_sidebar_mode::ZonesPitInfo) && + ui_building_assign_type && ui_building_assign_units && + ui_building_assign_is_marked && ui_building_assign_items && + ui_building_assign_type->size() == ui_building_assign_units->size() && + ui_building_item_cursor) + { + if (vector_get(*ui_building_assign_units, *ui_building_item_cursor)) + filter.initialize(ui->main.mode); + } + else + { + filter.deinitialize(); + } + + INTERPOSE_NEXT(render)(); + + filter.do_render(); + + } +}; + +zone_filter zone_hook::filter; + +IMPLEMENT_VMETHOD_INTERPOSE(zone_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(zone_hook, render); +//END zone filters + + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (!gps || !INTERPOSE_HOOK(zone_hook, feed).apply() || !INTERPOSE_HOOK(zone_hook, render).apply()) + out.printerr("Could not insert jobutils hooks!\n"); + + commands.push_back(PluginCommand( + "zone", "manage activity zones.", + df_zone, false, + zone_help.c_str() + )); + commands.push_back(PluginCommand( + "autonestbox", "auto-assign nestbox zones.", + df_autonestbox, false, + autonestbox_help.c_str() + )); + commands.push_back(PluginCommand( + "autobutcher", "auto-assign lifestock for butchering.", + df_autobutcher, false, + autobutcher_help.c_str() + )); + init_autobutcher(out); + init_autonestbox(out); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + cleanup_autobutcher(out); + cleanup_autonestbox(out); + return CR_OK; +} From 3095c43d2667f7d07388a72a359d6b1c8c2d85b6 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 29 Nov 2012 23:15:24 +1300 Subject: [PATCH 012/138] Fix location of workshop ui, based on menu width --- plugins/workflow.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 2d3746e71..8fa26d17f 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1628,6 +1628,17 @@ namespace wf_ui } } + static int get_left_margin() + { + int left_margin = gps->dimx - 30; + int8_t a = *df::global::ui_menu_width; + int8_t b = *df::global::ui_area_map_width; + if ((a == 1 && b > 1) || (a == 2 && b == 2)) + left_margin -= 24; + + return left_margin; + } + /* * Adjustment Dialog */ @@ -1640,7 +1651,7 @@ namespace wf_ui AdjustmentScreen(); void reset(); bool feed(set *input, ItemConstraint *cv, ProtectedJob *pj = NULL); - void render(ItemConstraint *cv); + void render(ItemConstraint *cv, bool in_monitor); protected: int32_t adjustment_ui_display_start; @@ -1762,9 +1773,9 @@ namespace wf_ui return true; } - void AdjustmentScreen::render(ItemConstraint *cv) + void AdjustmentScreen::render(ItemConstraint *cv, bool in_monitor) { - left_margin = gps->dimx - 30; + left_margin = (in_monitor) ? gps->dimx - 30 : get_left_margin(); x = left_margin; y = adjustment_ui_display_start; OutputString(COLOR_BROWN, x, y, "Workflow Settings", true, left_margin); @@ -2813,7 +2824,7 @@ namespace wf_ui if (rows.getDisplayListSize() > 0) { TableRow *row = rows.getFirstSelectedElem(); - AdjustmentScreen::render(row->cv); + AdjustmentScreen::render(row->cv, true); if (max_history_days > 0) { @@ -3022,14 +3033,14 @@ namespace wf_ui cv = pj->constraints[0]; } - dialog.render(cv); + dialog.render(cv, false); if (cv) ++dialog.y; OutputHotkeyString(dialog.left_margin, dialog.y, "Inventory Monitor", "m"); } else if (can_enable_plugin()) { - int x = gps->dimx - 30; + int x = get_left_margin(); int y = 24; OutputHotkeyString(x, y, "Enable Workflow", "w"); } From d59d1c652e34c9de2d15ad2f4111958e7833a8cb Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 30 Nov 2012 19:50:42 +1300 Subject: [PATCH 013/138] Fix gcc compile of search plugin --- plugins/search.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index ca2a1e9b1..da5a272e5 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -440,8 +440,8 @@ private: typedef search_hook stocks_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); // // END: Stocks screen search @@ -503,8 +503,8 @@ private: }; typedef search_hook unitlist_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search @@ -553,8 +553,8 @@ public: }; typedef search_hook trade_search_merc_hook; -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); class trade_search_fort : public trade_search_base @@ -577,8 +577,8 @@ public: }; typedef search_hook trade_search_fort_hook; -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); // // END: Trade screen search @@ -619,4 +619,4 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch trade_search_fort_hook::module.reset_all(); stocks_search_hook::module.reset_all(); return CR_OK; -} \ No newline at end of file +} From 745b347b5b5aa61f6ca2bb53d38c94df45597ae8 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 4 Dec 2012 10:30:49 +1300 Subject: [PATCH 014/138] Fix const correctness --- plugins/automaterial.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index ac5a4ae22..76664b91f 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -137,7 +137,7 @@ static inline MaterialDescriptor &get_last_used_material() return last_used_material[ui_build_selector->building_subtype]; } -static void set_last_used_material(MaterialDescriptor &matetial) +static void set_last_used_material(const MaterialDescriptor &matetial) { last_used_material[ui_build_selector->building_subtype] = matetial; } @@ -150,7 +150,7 @@ static MaterialDescriptor &get_last_moved_material() return last_moved_material[ui_build_selector->building_subtype]; } -static void set_last_moved_material(MaterialDescriptor &matetial) +static void set_last_moved_material(const MaterialDescriptor &matetial) { last_moved_material[ui_build_selector->building_subtype] = matetial; } @@ -402,4 +402,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 8 Dec 2012 09:51:35 +0400 Subject: [PATCH 015/138] Fix crash and confusing behavior in automaterial. --- plugins/automaterial.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index ac5a4ae22..d65d3dcf5 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -170,8 +170,8 @@ static MaterialDescriptor get_material_in_list(size_t i) } else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) { - result.item_type = gen->item_type; - result.item_subtype = gen->item_subtype; + result.item_type = spec->candidate->getType(); + result.item_subtype = spec->candidate->getSubtype(); result.type = spec->candidate->getActualMaterial(); result.index = spec->candidate->getActualMaterialIndex(); result.valid = true; @@ -316,7 +316,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { if (in_material_choice_stage()) { - if (!last_used_moved) + if (!last_used_moved && ui_build_selector->is_grouped) { if (auto_choose_materials && get_curr_constr_prefs().size() > 0) { @@ -326,7 +326,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } } - else if (ui_build_selector->is_grouped) + else { last_used_moved = true; move_material_to_top(get_last_used_material()); From 03aedf68954d7a017072da891c3a92d9e6d518c8 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 4 Dec 2012 10:30:49 +1300 Subject: [PATCH 016/138] Enabling designation like rectangular selection for constructions, with unlimited selection size and auto skipping of tiles that can't be built on. --- plugins/automaterial.cpp | 521 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 492 insertions(+), 29 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index d65d3dcf5..12dc1e01d 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -21,9 +21,20 @@ #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 "modules/Gui.h" #include "modules/Screen.h" +#include "modules/Items.h" +#include "modules/Constructions.h" +#include "modules/Buildings.h" +#include "modules/Maps.h" + +#include "TileTypes.h" +#include "df/job_item.h" using std::map; using std::string; @@ -64,6 +75,21 @@ static bool auto_choose_materials = true; static bool auto_choose_attempted = true; static bool revert_to_last_used_type = false; +struct point +{ + int32_t x, y, z; +}; + +static enum t_box_select_mode {SELECT_FIRST, SELECT_SECOND, SELECT_MATERIALS, AUTOSELECT_MATERIALS} box_select_mode = SELECT_FIRST; +static point box_first, box_second; +static bool box_select_enabled = false; +static bool show_box_selection = true; +static bool hollow_selection = false; +#define SELECTION_IGNORE_TICKS 10 +static int ignore_selection = SELECTION_IGNORE_TICKS; +static deque box_select_materials; +static vector building_sites; + static command_result automaterial_cmd(color_ostream &out, vector & parameters) { return CR_OK; @@ -95,6 +121,10 @@ void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bo OutputString(color, x, y, display, newline, left_margin); } +static string int_to_string(int i) +{ + return static_cast( &(ostringstream() << i))->str(); +} static inline bool in_material_choice_stage() { @@ -137,7 +167,7 @@ static inline MaterialDescriptor &get_last_used_material() return last_used_material[ui_build_selector->building_subtype]; } -static void set_last_used_material(MaterialDescriptor &matetial) +static void set_last_used_material(const MaterialDescriptor &matetial) { last_used_material[ui_build_selector->building_subtype] = matetial; } @@ -150,7 +180,7 @@ static MaterialDescriptor &get_last_moved_material() return last_moved_material[ui_build_selector->building_subtype]; } -static void set_last_moved_material(MaterialDescriptor &matetial) +static void set_last_moved_material(const MaterialDescriptor &matetial) { last_moved_material[ui_build_selector->building_subtype] = matetial; } @@ -240,12 +270,86 @@ static bool check_autoselect(MaterialDescriptor &material, bool toggle) } } +static void cancel_box_selection() +{ + if (box_select_mode == SELECT_FIRST) + return; + + box_select_mode = SELECT_FIRST; + box_select_materials.clear(); + if (!show_box_selection) + Gui::setDesignationCoords(-1, -1, -1); +} + +static bool is_valid_building_site(df::coord &pos) +{ + auto ttype = Maps::getTileType(pos); + if (!ttype || + *ttype == tiletype::Tree || + tileMaterial(*ttype) == tiletype_material::CONSTRUCTION || + tileMaterial(*ttype) == tiletype_material::AIR || + tileMaterial(*ttype) == tiletype_material::CAMPFIRE || + tileMaterial(*ttype) == tiletype_material::FIRE || + tileMaterial(*ttype) == tiletype_material::MAGMA || + tileMaterial(*ttype) == tiletype_material::DRIFTWOOD || + tileMaterial(*ttype) == tiletype_material::POOL || + tileMaterial(*ttype) == tiletype_material::BROOK || + tileMaterial(*ttype) == tiletype_material::RIVER + ) + return false; + + auto current = Buildings::findAtTile(pos); + if (current) + return false; + + df::coord2d size(1,1); + return Buildings::checkFreeTiles(pos, size, NULL, false, false); +} + +static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item) +{ + auto newinst = Buildings::allocInstance(pos, building_type::Construction, type); + if (!newinst) + return false; + + vector items; + items.push_back(item); + + if (!Buildings::constructWithItems(newinst, items)) + { + delete newinst; + return false; + } + + return true; +} + + struct jobutils_hook : public df::viewscreen_dwarfmodest { 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 select_material_at_index(size_t i) + { + ui_build_selector->sel_index = i; + std::set< df::interface_key > keys; + keys.insert(df::interface_key::SELECT_ALL); + this->feed(&keys); + return !in_material_choice_stage(); + } + bool choose_materials() { + if (!auto_choose_materials || get_curr_constr_prefs().size() == 0) + return false; + size_t size = ui_build_selector->choices.size(); for (size_t i = 0; i < size; i++) { @@ -253,31 +357,241 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest size_t j; if (is_material_in_autoselect(j, material)) { - ui_build_selector->sel_index = i; - std::set< df::interface_key > keys; - keys.insert(df::interface_key::SELECT_ALL); - this->feed(&keys); - if (!in_material_choice_stage()) - return true; + return select_material_at_index(i); } } return false; } - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + bool populate_box_materials(vector &gen_materials) + { + bool result = false; + + if (gen_materials.size() == 0) + return result; + + if (ui_build_selector->is_grouped) + send_key(interface_key::BUILDING_EXPAND_CONTRACT); + + size_t size = ui_build_selector->choices.size(); + vector::iterator gen_material; + for (size_t i = 0; i < size; i++) + { + if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) + { + for (gen_material = gen_materials.begin(); gen_material != gen_materials.end(); gen_material++) + { + if (gen_material->item_type == spec->candidate->getType() && + gen_material->item_subtype == spec->candidate->getSubtype() && + gen_material->type == spec->candidate->getActualMaterial() && + gen_material->index == spec->candidate->getActualMaterialIndex()) + { + box_select_materials.push_back(spec->candidate); + result = true; + break; + } + } + } + } + send_key(interface_key::BUILDING_EXPAND_CONTRACT); + + return result; + } + + void draw_box_selection() + { + if (!box_select_enabled) + return; + + df::coord vport = Gui::getViewportPos(); + + //Even if selection drawing is disabled, paint a green cursor as we can place box selection anywhere + + if (box_select_mode == SELECT_FIRST || (!show_box_selection && box_select_mode == SELECT_SECOND)) + { + int32_t x, y, z; + + if (!Gui::getCursorCoords(x, y, z)) + return; + + x = x - vport.x + 1; + y = y - vport.y + 1; + OutputString(COLOR_GREEN, x, y, "X"); + } + else if (show_box_selection && box_select_mode == SELECT_SECOND) + { + if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z)) + return; + + int32_t xD = (box_second.x > box_first.x) ? 1 : -1; + int32_t yD = (box_second.y > box_first.y) ? 1 : -1; + for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD) + { + for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD) + { + if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) + continue; + + int8_t color = (xB == box_second.x && yB == box_second.y) ? COLOR_GREEN : COLOR_BROWN; + + int32_t x = xB - vport.x + 1; + int32_t y = yB - vport.y + 1; + OutputString(color, x, y, "X"); + } + } + } + else if (show_box_selection && box_select_mode == SELECT_MATERIALS) + { + for (vector::iterator it = building_sites.begin(); it != building_sites.end(); it++) + { + int32_t x = it->x - vport.x + 1; + int32_t y = it->y - vport.y + 1; + OutputString(COLOR_GREEN, x, y, "X"); + } + } + } + + static void find_valid_building_sites() + { + building_sites.clear(); + + int xD = (box_second.x > box_first.x) ? 1 : -1; + int yD = (box_second.y > box_first.y) ? 1 : -1; + for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD) + { + for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD) + { + if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) + continue; + + df::coord pos(xB, yB, box_second.z); + if (is_valid_building_site(pos)) + building_sites.push_back(pos); + } + } + } + + void apply_box_selection(bool new_start) + { + static bool saved_revert_setting = false; + static bool auto_select_applied = false; + + box_select_mode = SELECT_MATERIALS; + if (new_start) + { + find_valid_building_sites(); + saved_revert_setting = revert_to_last_used_type; + revert_to_last_used_type = true; + auto_select_applied = false; + box_select_materials.clear(); + } + + while (building_sites.size() > 0) + { + df::coord pos = building_sites.back(); + building_sites.pop_back(); + if (box_select_materials.size() > 0) + { + df::construction_type type = (df::construction_type) ui_build_selector->building_subtype; + df::item *item = NULL; + while (box_select_materials.size() > 0) + { + item = box_select_materials.front(); + if (!item->flags.bits.in_job) + break; + box_select_materials.pop_front(); + item = NULL; + } + + if (item != NULL) + { + if (designate_new_construction(pos, type, item)) + { + box_select_materials.pop_front(); + box_select_mode = AUTOSELECT_MATERIALS; + send_key(interface_key::LEAVESCREEN); //Must do this to register items in use + send_key(hotkeys[type]); + box_select_mode = SELECT_MATERIALS; + } + continue; + } + } + + Gui::setCursorCoords(pos.x, pos.y, box_second.z); + send_key(interface_key::CURSOR_DOWN_Z); + send_key(interface_key::CURSOR_UP_Z); + send_key(df::interface_key::SELECT); + + if (in_material_choice_stage()) + { + if (!auto_select_applied) + { + auto_select_applied = true; + if (populate_box_materials(preferred_materials[ui_build_selector->building_subtype])) + { + building_sites.push_back(pos); //Retry current tile with auto select + continue; + } + } + + last_used_moved = false; + return; + } + } + + Gui::setCursorCoords(box_second.x, box_second.y, box_second.z); + send_key(interface_key::CURSOR_DOWN_Z); + send_key(interface_key::CURSOR_UP_Z); + + revert_to_last_used_type = saved_revert_setting; + if (!revert_to_last_used_type) + { + send_key(df::interface_key::LEAVESCREEN); + } + + cancel_box_selection(); + hollow_selection = false; + ignore_selection = 0; + } + + void reset_existing_selection() + { + for (int i = 0; i < 10; i++) + { + send_key(df::interface_key::BUILDING_DIM_Y_DOWN); + send_key(df::interface_key::BUILDING_DIM_X_DOWN); + } + } + + void handle_input(set *input) { if (in_material_choice_stage()) { + if (input->count(interface_key::LEAVESCREEN)) + { + box_select_mode = SELECT_FIRST; + } + MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); if (material.valid) { if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)) { if (get_last_moved_material().matches(material)) - last_used_moved = false; + last_used_moved = false; //Keep selected material on top set_last_used_material(material); + + if (box_select_enabled && input->count(interface_key::SEC_SELECT) && ui_build_selector->is_grouped) + { + auto curr_index = ui_build_selector->sel_index; + vector gen_material; + gen_material.push_back(get_material_in_list(curr_index)); + box_select_materials.clear(); + populate_box_materials(gen_material); + ui_build_selector->sel_index = curr_index; + } } else if (input->count(interface_key::CUSTOM_A)) { @@ -296,39 +610,141 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { revert_to_last_used_type = !revert_to_last_used_type; } + else if (input->count(interface_key::CUSTOM_B)) + { + box_select_enabled = !box_select_enabled; + if (!box_select_enabled) + cancel_box_selection(); + return; + } + else if (input->count(interface_key::LEAVESCREEN)) + { + switch (box_select_mode) + { + case SELECT_FIRST: + case SELECT_SECOND: + cancel_box_selection(); + + default: + break; + } + } + else if (box_select_enabled && + (ui_build_selector->building_subtype == construction_type::Wall || + ui_build_selector->building_subtype == construction_type::Fortification || + ui_build_selector->building_subtype == construction_type::Floor)) + { + if (input->count(interface_key::SELECT)) + { + switch (box_select_mode) + { + case SELECT_FIRST: + if (!Gui::getCursorCoords(box_first.x, box_first.y, box_first.z)) + { + cancel_box_selection(); + return; + } + box_select_mode = SELECT_SECOND; + if (!show_box_selection) + Gui::setDesignationCoords(box_first.x, box_first.y, box_first.z); + input->clear(); + return; + + case SELECT_SECOND: + if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z)) + { + cancel_box_selection(); + return; + } + cancel_box_selection(); + input->clear(); + apply_box_selection(true); + return; + + default: + break; + } + } + else if (input->count(interface_key::CUSTOM_X)) + { + show_box_selection = !show_box_selection; + if (box_select_mode == SELECT_SECOND) + { + if (show_box_selection) + { + Gui::setDesignationCoords(-1, -1, -1); + } + else + { + Gui::setDesignationCoords(box_first.x, box_first.y, box_first.z); + } + } + } + else if (input->count(interface_key::CUSTOM_H)) + { + hollow_selection = !hollow_selection; + } + else if (input->count(interface_key::BUILDING_DIM_Y_UP) || + input->count(interface_key::BUILDING_DIM_Y_DOWN) || + input->count(interface_key::BUILDING_DIM_X_UP) || + input->count(interface_key::BUILDING_DIM_X_DOWN)) + { + input->clear(); + return; + } + } + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (ignore_selection < SELECTION_IGNORE_TICKS) + { + //FIXME: Sometimes there's an extra ENTER key left over after box selection + ignore_selection = SELECTION_IGNORE_TICKS; + return; } + if (box_select_mode != AUTOSELECT_MATERIALS) + handle_input(input); + int16_t last_used_constr_subtype = (in_material_choice_stage()) ? ui_build_selector->building_subtype : -1; INTERPOSE_NEXT(feed)(input); if (revert_to_last_used_type && last_used_constr_subtype >= 0 && - !in_material_choice_stage() && + in_type_choice_stage() && hotkeys.find(last_used_constr_subtype) != hotkeys.end()) { input->clear(); input->insert(hotkeys[last_used_constr_subtype]); - this->feed(input); + INTERPOSE_NEXT(feed)(input); + + if (box_select_mode == SELECT_MATERIALS) + { + apply_box_selection(false); + } } } DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + if (ignore_selection < SELECTION_IGNORE_TICKS) + { + ++ignore_selection; + } + if (in_material_choice_stage()) { if (!last_used_moved && ui_build_selector->is_grouped) { - if (auto_choose_materials && get_curr_constr_prefs().size() > 0) + last_used_moved = true; + if (choose_materials()) { - last_used_moved = true; - if (choose_materials()) - { - return; - } + return; } else { - last_used_moved = true; move_material_to_top(get_last_used_material()); } } @@ -344,15 +760,23 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest INTERPOSE_NEXT(render)(); + draw_box_selection(); + + if (in_type_choice_stage()) + { + cancel_box_selection(); + return; + } + + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 25; if (in_material_choice_stage()) { MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); if (material.valid) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; - string toggle_string = "Enable"; string title = "Disabled"; if (check_autoselect(material, false)) @@ -363,20 +787,59 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest OutputString(COLOR_BROWN, x, y, "DFHack Autoselect: " + title, true, left_margin); OutputHotkeyString(x, y, toggle_string.c_str(), "a", true, left_margin); + + if (box_select_mode == SELECT_MATERIALS) + { + ++y; + OutputString(COLOR_BROWN, x, y, "Construction:", true, left_margin); + OutputString(COLOR_WHITE, x, y, int_to_string(building_sites.size() + 1) + " tiles to fill", true, left_margin); + } } } - else if (in_placement_stage() && ui_build_selector->building_subtype != 7) + else if (in_placement_stage() && ui_build_selector->building_subtype < 7) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; - string autoselect_toggle_string = (auto_choose_materials) ? "Disable Auto Mat-select" : "Enable Auto Mat-select"; string revert_toggle_string = (revert_to_last_used_type) ? "Disable Auto Type-select" : "Enable Auto Type-select"; OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); OutputHotkeyString(x, y, autoselect_toggle_string.c_str(), "a", true, left_margin); OutputHotkeyString(x, y, revert_toggle_string.c_str(), "t", true, left_margin); + + if (ui_build_selector->building_subtype == construction_type::Wall || + ui_build_selector->building_subtype == construction_type::Fortification || + ui_build_selector->building_subtype == construction_type::Floor) + { + ++y; + OutputHotkeyString(x, y, (box_select_enabled) ? "Disable Box Select" : "Enable Box Select", "b", true, left_margin); + if (box_select_enabled) + { + OutputHotkeyString(x, y, (show_box_selection) ? "Disable Box Marking" : "Enable Box Marking", "x", true, left_margin); + OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); + } + ++y; + if (box_select_enabled) + { + Screen::Pen pen(' ',COLOR_BLACK); + y = dims.y1 + 2; + Screen::fillRect(pen, x, y, dims.menu_x2, y + 17); + + y += 2; + switch (box_select_mode) + { + case SELECT_FIRST: + OutputString(COLOR_BROWN, x, y, "Choose first corner", true, left_margin); + break; + + case SELECT_SECOND: + OutputString(COLOR_GREEN, x, y, "Choose second corner", true, left_margin); + int cx = box_first.x; + int cy = box_first.y; + OutputString(COLOR_BROWN, cx, cy, "X"); + } + + OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); + } + } } } }; @@ -402,4 +865,4 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sun, 23 Dec 2012 01:09:21 -0800 Subject: [PATCH 017/138] Added restrictliquids and restrictice commands. --- plugins/filltraffic.cpp | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/plugins/filltraffic.cpp b/plugins/filltraffic.cpp index 6e87fd854..d7e528c92 100644 --- a/plugins/filltraffic.cpp +++ b/plugins/filltraffic.cpp @@ -23,6 +23,8 @@ typedef void (*checkTile)(DFCoord, MapExtras::MapCache &); //Forward Declarations for Commands command_result filltraffic(color_ostream &out, std::vector & params); command_result alltraffic(color_ostream &out, std::vector & params); +command_result restrictLiquid(color_ostream &out, std::vector & params); +command_result restrictIce(color_ostream &out, std::vector & params); //Forward Declarations for Utility Functions command_result setAllMatching(color_ostream &out, checkTile checkProc, @@ -34,6 +36,9 @@ void allNormal(DFCoord coord, MapExtras::MapCache & map); void allLow(DFCoord coord, MapExtras::MapCache & map); void allRestricted(DFCoord coord, MapExtras::MapCache & map); +void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map); +void restrictIceProc(DFCoord coord, MapExtras::MapCache &map); + DFHACK_PLUGIN("filltraffic"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) @@ -66,6 +71,14 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & params) return setAllMatching(out, proc); } +command_result restrictLiquid(color_ostream &out, std::vector & params) +{ + return setAllMatching(out, restrictLiquidProc); +} + +command_result restrictIce(color_ostream &out, std::vector & params) +{ + return setAllMatching(out, restrictIceProc); +} + //Helper function for writing new functions that check every tile on the map. //newTraffic is the traffic designation to set. //check takes a coordinate and the map cache as arguments, and returns true if the criteria is met. @@ -356,3 +379,33 @@ void allRestricted(DFCoord coord, MapExtras::MapCache &map) des.bits.traffic = tile_traffic::Restricted; map.setDesignationAt(coord, des); } + +//Restrict traffic if tile is visible and liquid is present. +void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map) +{ + df::tile_designation des = map.designationAt(coord); + if ((des.bits.hidden == 0) && (des.bits.flow_size != 0)) + { + des.bits.traffic = tile_traffic::Restricted; + map.setDesignationAt(coord, des); + } +} + +//Restrict traffice if tile is above visible ice wall. +void restrictIceProc(DFCoord coord, MapExtras::MapCache &map) +{ + //There is no ice below the bottom of the map. + if (coord.z == 0) + return; + + DFCoord tile_below = DFCoord(coord.x, coord.y, coord.z - 1); + df::tiletype tt = map.tiletypeAt(tile_below); + df::tile_designation des = map.designationAt(tile_below); + + if ((des.bits.hidden == 0) && (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID)) + { + des = map.designationAt(coord); + des.bits.traffic = tile_traffic::Restricted; + map.setDesignationAt(coord, des); + } +} From 5e54c9919a430609198936d107802075da0aed03 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 24 Dec 2012 00:59:12 +1300 Subject: [PATCH 018/138] Improved check for figuring out what tiles can have constructions. --- plugins/automaterial.cpp | 86 +++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 12dc1e01d..67c5d4dd6 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -281,23 +281,79 @@ static void cancel_box_selection() Gui::setDesignationCoords(-1, -1, -1); } -static bool is_valid_building_site(df::coord &pos) +static bool is_valid_building_site(df::coord &pos, bool orthogonal_check) { auto ttype = Maps::getTileType(pos); - if (!ttype || - *ttype == tiletype::Tree || - tileMaterial(*ttype) == tiletype_material::CONSTRUCTION || - tileMaterial(*ttype) == tiletype_material::AIR || - tileMaterial(*ttype) == tiletype_material::CAMPFIRE || - tileMaterial(*ttype) == tiletype_material::FIRE || - tileMaterial(*ttype) == tiletype_material::MAGMA || - tileMaterial(*ttype) == tiletype_material::DRIFTWOOD || - tileMaterial(*ttype) == tiletype_material::POOL || - tileMaterial(*ttype) == tiletype_material::BROOK || - tileMaterial(*ttype) == tiletype_material::RIVER - ) + + if (!ttype) + return false; + + auto shape = tileShape(*ttype); + auto shapeBasic = tileShapeBasic(shape); + + if (shapeBasic == tiletype_shape_basic::Open) + { + if (orthogonal_check) + return false; + + bool valid_orthogonal_tile_found = false; + df::coord orthagonal_pos; + orthagonal_pos.z = pos.z; + for (orthagonal_pos.x = pos.x-1; orthagonal_pos.x <= pos.x+1 && !valid_orthogonal_tile_found; orthagonal_pos.x++) + { + for (orthagonal_pos.y = pos.y-1; orthagonal_pos.y <= pos.y+1; orthagonal_pos.y++) + { + if (pos.x != orthagonal_pos.x && pos.y != orthagonal_pos.y) + continue; + + if (Maps::isValidTilePos(orthagonal_pos) && is_valid_building_site(orthagonal_pos, true)) + { + valid_orthogonal_tile_found = true; + break; + } + + } + } + + if (!valid_orthogonal_tile_found) + return false; + } + else + { + auto material = tileMaterial(*ttype); + if (shape == tiletype_shape::RAMP) + { + if (material == tiletype_material::CONSTRUCTION) + return false; + } + else + { + if (shapeBasic != tiletype_shape_basic::Floor) + return false; + + if (shape == tiletype_shape::TREE) + return false; + + if (material == tiletype_material::FIRE || + material == tiletype_material::POOL || + material == tiletype_material::BROOK || + material == tiletype_material::RIVER || + material == tiletype_material::MAGMA || + material == tiletype_material::DRIFTWOOD || + material == tiletype_material::CAMPFIRE + ) + + return false; + } + } + + auto designation = Maps::getTileDesignation(pos); + if (designation->bits.flow_size > 2) return false; + if (orthogonal_check) + return true; + auto current = Buildings::findAtTile(pos); if (current) return false; @@ -466,7 +522,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest continue; df::coord pos(xB, yB, box_second.z); - if (is_valid_building_site(pos)) + if (is_valid_building_site(pos, false)) building_sites.push_back(pos); } } @@ -612,9 +668,11 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } else if (input->count(interface_key::CUSTOM_B)) { + reset_existing_selection(); box_select_enabled = !box_select_enabled; if (!box_select_enabled) cancel_box_selection(); + return; } else if (input->count(interface_key::LEAVESCREEN)) From 40e25b6a1f7156bc09fe2ce56f473eeb37712088 Mon Sep 17 00:00:00 2001 From: falconne Date: Mon, 24 Dec 2012 08:58:03 +1300 Subject: [PATCH 019/138] Better handling of open space construction placement --- plugins/automaterial.cpp | 488 ++++++++++++++++++++++++--------------- 1 file changed, 308 insertions(+), 180 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 67c5d4dd6..35d35d7a2 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -66,29 +66,6 @@ struct MaterialDescriptor } }; -static map last_used_material; -static map last_moved_material; -static map< int16_t, vector > preferred_materials; -static map< int16_t, df::interface_key > hotkeys; -static bool last_used_moved = false; -static bool auto_choose_materials = true; -static bool auto_choose_attempted = true; -static bool revert_to_last_used_type = false; - -struct point -{ - int32_t x, y, z; -}; - -static enum t_box_select_mode {SELECT_FIRST, SELECT_SECOND, SELECT_MATERIALS, AUTOSELECT_MATERIALS} box_select_mode = SELECT_FIRST; -static point box_first, box_second; -static bool box_select_enabled = false; -static bool show_box_selection = true; -static bool hollow_selection = false; -#define SELECTION_IGNORE_TICKS 10 -static int ignore_selection = SELECTION_IGNORE_TICKS; -static deque box_select_materials; -static vector building_sites; static command_result automaterial_cmd(color_ostream &out, vector & parameters) { @@ -100,7 +77,6 @@ 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); @@ -126,6 +102,31 @@ static string int_to_string(int i) return static_cast( &(ostringstream() << i))->str(); } +//START UI Functions +struct coord32_t +{ + int32_t x, y, z; +}; + +static enum t_box_select_mode {SELECT_FIRST, SELECT_SECOND, SELECT_MATERIALS, AUTOSELECT_MATERIALS} box_select_mode = SELECT_FIRST; +static coord32_t box_first, box_second; +static bool box_select_enabled = false; +static bool show_box_selection = true; +static bool hollow_selection = false; +static deque box_select_materials; + +#define SELECTION_IGNORE_TICKS 10 +static int ignore_selection = SELECTION_IGNORE_TICKS; + +static map last_used_material; +static map last_moved_material; +static map< int16_t, vector > preferred_materials; +static map< int16_t, df::interface_key > hotkeys; +static bool last_used_moved = false; +static bool auto_choose_materials = true; +static bool auto_choose_attempted = true; +static bool revert_to_last_used_type = false; + static inline bool in_material_choice_stage() { return Gui::build_selector_hotkey(Core::getTopViewscreen()) && @@ -280,10 +281,48 @@ static void cancel_box_selection() if (!show_box_selection) Gui::setDesignationCoords(-1, -1, -1); } +//END UI Functions -static bool is_valid_building_site(df::coord &pos, bool orthogonal_check) +//START Building and Verification +struct building_site { - auto ttype = Maps::getTileType(pos); + df::coord pos; + bool in_open_air; + + building_site(df::coord pos, bool in_open_air) + { + this->pos = pos; + this->in_open_air = in_open_air; + } + + building_site() {} +}; + +static deque valid_building_sites; +static deque open_air_sites; +static building_site anchor; +static bool in_future_placement_mode = false; + +static bool is_orthogonal_to_pending_construction(building_site &site) +{ + for (deque::iterator it = valid_building_sites.begin(); it != valid_building_sites.end(); it++) + { + if ((it->pos.x == site.pos.x && abs(it->pos.y - site.pos.y) == 1) || (it->pos.y == site.pos.y && abs(it->pos.x - site.pos.x) == 1)) + { + site.in_open_air = true; + return true; + } + } + + return false; +} + +static bool is_valid_building_site(building_site &site, bool orthogonal_check) +{ + if (!Maps::isValidTilePos(site.pos)) + return false; + + auto ttype = Maps::getTileType(site.pos); if (!ttype) return false; @@ -294,29 +333,51 @@ static bool is_valid_building_site(df::coord &pos, bool orthogonal_check) if (shapeBasic == tiletype_shape_basic::Open) { if (orthogonal_check) + { + if (!in_future_placement_mode) + return false; + + auto current = Buildings::findAtTile(site.pos); + if (current) + { + auto cons = strict_virtual_cast(current); + if (!cons || cons->type != construction_type::Floor) + return false; + + site.in_open_air = true; + return true; + } + return false; + } bool valid_orthogonal_tile_found = false; df::coord orthagonal_pos; - orthagonal_pos.z = pos.z; - for (orthagonal_pos.x = pos.x-1; orthagonal_pos.x <= pos.x+1 && !valid_orthogonal_tile_found; orthagonal_pos.x++) + orthagonal_pos.z = site.pos.z; + for (orthagonal_pos.x = site.pos.x-1; orthagonal_pos.x <= site.pos.x+1 && !valid_orthogonal_tile_found; orthagonal_pos.x++) { - for (orthagonal_pos.y = pos.y-1; orthagonal_pos.y <= pos.y+1; orthagonal_pos.y++) + for (orthagonal_pos.y = site.pos.y-1; orthagonal_pos.y <= site.pos.y+1; orthagonal_pos.y++) { - if (pos.x != orthagonal_pos.x && pos.y != orthagonal_pos.y) + if ((site.pos.x == orthagonal_pos.x) == (site.pos.y == orthagonal_pos.y)) continue; - if (Maps::isValidTilePos(orthagonal_pos) && is_valid_building_site(orthagonal_pos, true)) + building_site orthogonal_site(orthagonal_pos, false); + if (is_valid_building_site(orthogonal_site, true)) { valid_orthogonal_tile_found = true; + if (orthogonal_site.in_open_air) + site.in_open_air = true; break; } } } - if (!valid_orthogonal_tile_found) + if (!valid_orthogonal_tile_found && !is_orthogonal_to_pending_construction(site)) + { + site.in_open_air = true; return false; + } } else { @@ -347,19 +408,19 @@ static bool is_valid_building_site(df::coord &pos, bool orthogonal_check) } } - auto designation = Maps::getTileDesignation(pos); + auto designation = Maps::getTileDesignation(site.pos); if (designation->bits.flow_size > 2) return false; if (orthogonal_check) return true; - auto current = Buildings::findAtTile(pos); + auto current = Buildings::findAtTile(site.pos); if (current) return false; df::coord2d size(1,1); - return Buildings::checkFreeTiles(pos, size, NULL, false, false); + return Buildings::checkFreeTiles(site.pos, size, NULL, false, false); } static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item) @@ -379,10 +440,13 @@ static bool designate_new_construction(df::coord &pos, df::construction_type &ty return true; } +//END Building and Verification +//START Viewscreen Hook struct jobutils_hook : public df::viewscreen_dwarfmodest { + //START UI Methods typedef df::viewscreen_dwarfmodest interpose_base; void send_key(const df::interface_key &key) @@ -420,41 +484,6 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return false; } - bool populate_box_materials(vector &gen_materials) - { - bool result = false; - - if (gen_materials.size() == 0) - return result; - - if (ui_build_selector->is_grouped) - send_key(interface_key::BUILDING_EXPAND_CONTRACT); - - size_t size = ui_build_selector->choices.size(); - vector::iterator gen_material; - for (size_t i = 0; i < size; i++) - { - if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) - { - for (gen_material = gen_materials.begin(); gen_material != gen_materials.end(); gen_material++) - { - if (gen_material->item_type == spec->candidate->getType() && - gen_material->item_subtype == spec->candidate->getSubtype() && - gen_material->type == spec->candidate->getActualMaterial() && - gen_material->index == spec->candidate->getActualMaterialIndex()) - { - box_select_materials.push_back(spec->candidate); - result = true; - break; - } - } - } - } - send_key(interface_key::BUILDING_EXPAND_CONTRACT); - - return result; - } - void draw_box_selection() { if (!box_select_enabled) @@ -499,118 +528,15 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } else if (show_box_selection && box_select_mode == SELECT_MATERIALS) { - for (vector::iterator it = building_sites.begin(); it != building_sites.end(); it++) + for (deque::iterator it = valid_building_sites.begin(); it != valid_building_sites.end(); it++) { - int32_t x = it->x - vport.x + 1; - int32_t y = it->y - vport.y + 1; + int32_t x = it->pos.x - vport.x + 1; + int32_t y = it->pos.y - vport.y + 1; OutputString(COLOR_GREEN, x, y, "X"); } } } - static void find_valid_building_sites() - { - building_sites.clear(); - - int xD = (box_second.x > box_first.x) ? 1 : -1; - int yD = (box_second.y > box_first.y) ? 1 : -1; - for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD) - { - for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD) - { - if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) - continue; - - df::coord pos(xB, yB, box_second.z); - if (is_valid_building_site(pos, false)) - building_sites.push_back(pos); - } - } - } - - void apply_box_selection(bool new_start) - { - static bool saved_revert_setting = false; - static bool auto_select_applied = false; - - box_select_mode = SELECT_MATERIALS; - if (new_start) - { - find_valid_building_sites(); - saved_revert_setting = revert_to_last_used_type; - revert_to_last_used_type = true; - auto_select_applied = false; - box_select_materials.clear(); - } - - while (building_sites.size() > 0) - { - df::coord pos = building_sites.back(); - building_sites.pop_back(); - if (box_select_materials.size() > 0) - { - df::construction_type type = (df::construction_type) ui_build_selector->building_subtype; - df::item *item = NULL; - while (box_select_materials.size() > 0) - { - item = box_select_materials.front(); - if (!item->flags.bits.in_job) - break; - box_select_materials.pop_front(); - item = NULL; - } - - if (item != NULL) - { - if (designate_new_construction(pos, type, item)) - { - box_select_materials.pop_front(); - box_select_mode = AUTOSELECT_MATERIALS; - send_key(interface_key::LEAVESCREEN); //Must do this to register items in use - send_key(hotkeys[type]); - box_select_mode = SELECT_MATERIALS; - } - continue; - } - } - - Gui::setCursorCoords(pos.x, pos.y, box_second.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); - send_key(df::interface_key::SELECT); - - if (in_material_choice_stage()) - { - if (!auto_select_applied) - { - auto_select_applied = true; - if (populate_box_materials(preferred_materials[ui_build_selector->building_subtype])) - { - building_sites.push_back(pos); //Retry current tile with auto select - continue; - } - } - - last_used_moved = false; - return; - } - } - - Gui::setCursorCoords(box_second.x, box_second.y, box_second.z); - send_key(interface_key::CURSOR_DOWN_Z); - send_key(interface_key::CURSOR_UP_Z); - - revert_to_last_used_type = saved_revert_setting; - if (!revert_to_last_used_type) - { - send_key(df::interface_key::LEAVESCREEN); - } - - cancel_box_selection(); - hollow_selection = false; - ignore_selection = 0; - } - void reset_existing_selection() { for (int i = 0; i < 10; i++) @@ -639,14 +565,17 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest set_last_used_material(material); - if (box_select_enabled && input->count(interface_key::SEC_SELECT) && ui_build_selector->is_grouped) + if (box_select_enabled) { auto curr_index = ui_build_selector->sel_index; vector gen_material; gen_material.push_back(get_material_in_list(curr_index)); box_select_materials.clear(); - populate_box_materials(gen_material); - ui_build_selector->sel_index = curr_index; + // Populate material list with selected material + populate_box_materials(gen_material, ((input->count(interface_key::SEC_SELECT) && ui_build_selector->is_grouped) ? -1 : 1)); + + input->clear(); // Let the apply_box_selection routine allocate the construction + input->insert(interface_key::LEAVESCREEN); } } else if (input->count(interface_key::CUSTOM_A)) @@ -753,6 +682,204 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } } } + //END UI Methods + + //START Building Application + bool populate_box_materials(vector &gen_materials, int32_t count = -1) + { + bool result = false; + + if (gen_materials.size() == 0) + return result; + + if (ui_build_selector->is_grouped) + send_key(interface_key::BUILDING_EXPAND_CONTRACT); + + size_t size = ui_build_selector->choices.size(); + vector::iterator gen_material; + for (size_t i = 0; i < size; i++) + { + if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) + { + for (gen_material = gen_materials.begin(); gen_material != gen_materials.end(); gen_material++) + { + if (gen_material->item_type == spec->candidate->getType() && + gen_material->item_subtype == spec->candidate->getSubtype() && + gen_material->type == spec->candidate->getActualMaterial() && + gen_material->index == spec->candidate->getActualMaterialIndex()) + { + box_select_materials.push_back(spec->candidate); + if (count > -1) + return true; // Right now we only support 1 or all materials + + result = true; + break; + } + } + } + } + send_key(interface_key::BUILDING_EXPAND_CONTRACT); + + return result; + } + + void move_cursor(df::coord &pos) + { + Gui::setCursorCoords(pos.x, pos.y, pos.z); + send_key(interface_key::CURSOR_DOWN_Z); + send_key(interface_key::CURSOR_UP_Z); + } + + void move_cursor(coord32_t &pos) + { + move_cursor(df::coord((int16_t) pos.x, (int16_t) pos.y, (int16_t) pos.z)); + } + + static bool find_valid_building_sites() + { + valid_building_sites.clear(); + open_air_sites.clear(); + + int xD = (box_second.x > box_first.x) ? 1 : -1; + int yD = (box_second.y > box_first.y) ? 1 : -1; + for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD) + { + for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD) + { + if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) + continue; + + building_site site(df::coord(xB, yB, box_second.z), false); + if (is_valid_building_site(site, false)) + valid_building_sites.push_back(site); + else if (site.in_open_air) + open_air_sites.push_back(site); + } + } + + size_t last_open_air_count = 0; + while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) + { + last_open_air_count = open_air_sites.size(); + deque current_open_air_list = open_air_sites; + open_air_sites.clear(); + for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) + { + if (is_orthogonal_to_pending_construction(*it)) + valid_building_sites.push_back(*it); + else + open_air_sites.push_back(*it); + } + + } + + return valid_building_sites.size() > 0; + } + + void apply_box_selection(bool new_start) + { + static bool saved_revert_setting = false; + static bool auto_select_applied = false; + + box_select_mode = SELECT_MATERIALS; + if (new_start) + { + if (!find_valid_building_sites()) + { + cancel_box_selection(); + hollow_selection = false; + return; + } + + saved_revert_setting = revert_to_last_used_type; + revert_to_last_used_type = true; + auto_select_applied = false; + box_select_materials.clear(); + + // First valid site is guaranteed to be anchored, either on a tile or against a valid orthogonal tile + // Use it as an anchor point to generate materials list + anchor = valid_building_sites.front(); + valid_building_sites.pop_front(); + valid_building_sites.push_back(anchor); + } + + while (valid_building_sites.size() > 0) + { + building_site site = valid_building_sites.front(); + valid_building_sites.pop_front(); + if (box_select_materials.size() > 0) + { + df::construction_type type = (df::construction_type) ui_build_selector->building_subtype; + df::item *item = NULL; + while (box_select_materials.size() > 0) + { + item = box_select_materials.front(); + if (!item->flags.bits.in_job) + break; + box_select_materials.pop_front(); + item = NULL; + } + + if (item != NULL) + { + if (designate_new_construction(site.pos, type, item)) + { + box_select_materials.pop_front(); + box_select_mode = AUTOSELECT_MATERIALS; + send_key(interface_key::LEAVESCREEN); //Must do this to register items in use + send_key(hotkeys[type]); + box_select_mode = SELECT_MATERIALS; + } + continue; + } + } + + // Generate material list using regular construction placement routine + + if (site.in_open_air) + { + // Cannot invoke material selection on an unconnected tile, use anchor instead + move_cursor(anchor.pos); + send_key(df::interface_key::SELECT); + } + + move_cursor(site.pos); + + if (!site.in_open_air) + send_key(df::interface_key::SELECT); + + if (in_material_choice_stage()) + { + valid_building_sites.push_front(site); //Redo current tile with whatever gets selected + if (!auto_select_applied) + { + // See if any auto select materials are available + auto_select_applied = true; + if (auto_choose_materials && populate_box_materials(preferred_materials[ui_build_selector->building_subtype])) + { + continue; + } + } + + last_used_moved = false; + return; // No auto select materials left, ask user + } + } + + // Allocation done, reset + move_cursor(box_second); + + revert_to_last_used_type = saved_revert_setting; + if (!revert_to_last_used_type) + { + send_key(df::interface_key::LEAVESCREEN); + } + + cancel_box_selection(); + hollow_selection = false; + ignore_selection = 0; + } + //END Building Application DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { @@ -850,7 +977,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { ++y; OutputString(COLOR_BROWN, x, y, "Construction:", true, left_margin); - OutputString(COLOR_WHITE, x, y, int_to_string(building_sites.size() + 1) + " tiles to fill", true, left_margin); + OutputString(COLOR_WHITE, x, y, int_to_string(valid_building_sites.size() + 1) + " tiles to fill", true, left_margin); } } } @@ -901,6 +1028,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } } }; +//END Viewscreen Hook color_ostream_proxy console_out(Core::getInstance().getConsole()); From 1ed129e1a7a723aa9228f8a4f075b4d40a787d75 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 25 Dec 2012 21:59:27 +1300 Subject: [PATCH 020/138] Allow placing constructions in unconnected open space, with sensible restrictions --- plugins/automaterial.cpp | 270 ++++++++++++++++++++++++++------------- 1 file changed, 182 insertions(+), 88 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 35d35d7a2..9f943774e 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -126,6 +126,7 @@ static bool last_used_moved = false; static bool auto_choose_materials = true; static bool auto_choose_attempted = true; static bool revert_to_last_used_type = false; +static bool allow_future_placement = false; static inline bool in_material_choice_stage() { @@ -283,6 +284,7 @@ static void cancel_box_selection() } //END UI Functions + //START Building and Verification struct building_site { @@ -301,7 +303,6 @@ struct building_site static deque valid_building_sites; static deque open_air_sites; static building_site anchor; -static bool in_future_placement_mode = false; static bool is_orthogonal_to_pending_construction(building_site &site) { @@ -317,7 +318,7 @@ static bool is_orthogonal_to_pending_construction(building_site &site) return false; } -static bool is_valid_building_site(building_site &site, bool orthogonal_check) +static bool is_valid_building_site(building_site &site, bool orthogonal_check, bool check_placed_constructions, bool in_future_placement_mode) { if (!Maps::isValidTilePos(site.pos)) return false; @@ -362,7 +363,7 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check) continue; building_site orthogonal_site(orthagonal_pos, false); - if (is_valid_building_site(orthogonal_site, true)) + if (is_valid_building_site(orthogonal_site, true, check_placed_constructions, in_future_placement_mode)) { valid_orthogonal_tile_found = true; if (orthogonal_site.in_open_air) @@ -373,12 +374,17 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check) } } - if (!valid_orthogonal_tile_found && !is_orthogonal_to_pending_construction(site)) + if (!(valid_orthogonal_tile_found || (check_placed_constructions && is_orthogonal_to_pending_construction(site)))) { site.in_open_air = true; return false; } } + else if (orthogonal_check) + { + if (shape != tiletype_shape::RAMP && shapeBasic != tiletype_shape_basic::Floor) + return false; + } else { auto material = tileMaterial(*ttype); @@ -392,6 +398,24 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check) if (shapeBasic != tiletype_shape_basic::Floor) return false; + if (material == tiletype_material::CONSTRUCTION) + { + // Can build on top of a wall, but not on a constructed floor + df::coord pos_below = site.pos; + pos_below.z--; + if (!Maps::isValidTilePos(pos_below)) + return false; + + auto ttype = Maps::getTileType(pos_below); + if (!ttype) + return false; + + auto shape = tileShape(*ttype); + auto shapeBasic = tileShapeBasic(shape); + if (tileShapeBasic(shape) != tiletype_shape_basic::Wall) + return false; + } + if (shape == tiletype_shape::TREE) return false; @@ -408,13 +432,13 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check) } } + if (orthogonal_check) + return true; + auto designation = Maps::getTileDesignation(site.pos); if (designation->bits.flow_size > 2) return false; - if (orthogonal_check) - return true; - auto current = Buildings::findAtTile(site.pos); if (current) return false; @@ -423,6 +447,95 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check) return Buildings::checkFreeTiles(site.pos, size, NULL, false, false); } + +static bool find_anchor_in_spiral(const df::coord &start) +{ + bool found = false; + + for (anchor.pos.z = start.z; anchor.pos.z > start.z - 4; anchor.pos.z--) + { + int x, y, dx, dy; + x = y = dx = 0; + dy = -1; + const int side = 11; + const int maxI = side*side; + for (int i = 0; i < maxI; i++) + { + if (-side/2 < x && x <= side/2 && -side/2 < y && y <= side/2) + { + anchor.pos.x = start.x + x; + anchor.pos.y = start.y + y; + if (is_valid_building_site(anchor, false, false, false)) + { + found = true; + break; + } + } + + if ((x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1-y))) + { + int tmp = dx; + dx = -dy; + dy = tmp; + } + + x += dx; + y += dy; + } + + if (found) + break; + } + + return found; +} + +static bool find_valid_building_sites(bool in_future_placement_mode) +{ + valid_building_sites.clear(); + open_air_sites.clear(); + + int xD = (box_second.x > box_first.x) ? 1 : -1; + int yD = (box_second.y > box_first.y) ? 1 : -1; + for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD) + { + for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD) + { + if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) + continue; + + building_site site(df::coord(xB, yB, box_second.z), false); + if (is_valid_building_site(site, false, true, in_future_placement_mode)) + valid_building_sites.push_back(site); + else if (site.in_open_air) + { + if (in_future_placement_mode) + valid_building_sites.push_back(site); + else + open_air_sites.push_back(site); + } + } + } + + size_t last_open_air_count = 0; + while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) + { + last_open_air_count = open_air_sites.size(); + deque current_open_air_list = open_air_sites; + open_air_sites.clear(); + for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) + { + if (is_orthogonal_to_pending_construction(*it)) + valid_building_sites.push_back(*it); + else + open_air_sites.push_back(*it); + } + + } + + return valid_building_sites.size() > 0; +} + static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item) { auto newinst = Buildings::allocInstance(pos, building_type::Construction, type); @@ -548,6 +661,9 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest void handle_input(set *input) { + if (ui_build_selector->building_subtype >= 7) + return; + if (in_material_choice_stage()) { if (input->count(interface_key::LEAVESCREEN)) @@ -604,6 +720,10 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } + else if (input->count(interface_key::CUSTOM_O)) + { + allow_future_placement = !allow_future_placement; + } else if (input->count(interface_key::LEAVESCREEN)) { switch (box_select_mode) @@ -616,10 +736,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest break; } } - else if (box_select_enabled && - (ui_build_selector->building_subtype == construction_type::Wall || - ui_build_selector->building_subtype == construction_type::Fortification || - ui_build_selector->building_subtype == construction_type::Floor)) + else if (box_select_enabled) { if (input->count(interface_key::SELECT)) { @@ -735,56 +852,42 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest move_cursor(df::coord((int16_t) pos.x, (int16_t) pos.y, (int16_t) pos.z)); } - static bool find_valid_building_sites() + void apply_box_selection(bool new_start) { - valid_building_sites.clear(); - open_air_sites.clear(); + static bool saved_revert_setting = false; + static bool auto_select_applied = false; - int xD = (box_second.x > box_first.x) ? 1 : -1; - int yD = (box_second.y > box_first.y) ? 1 : -1; - for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD) + box_select_mode = SELECT_MATERIALS; + if (new_start) { - for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD) + bool ok_to_continue = false; + bool in_future_placement_mode = false; + if (!find_valid_building_sites(false)) { - if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) - continue; - - building_site site(df::coord(xB, yB, box_second.z), false); - if (is_valid_building_site(site, false)) - valid_building_sites.push_back(site); - else if (site.in_open_air) - open_air_sites.push_back(site); + if (allow_future_placement) + { + in_future_placement_mode = find_valid_building_sites(true); + } } - } - - size_t last_open_air_count = 0; - while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) - { - last_open_air_count = open_air_sites.size(); - deque current_open_air_list = open_air_sites; - open_air_sites.clear(); - for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) + else { - if (is_orthogonal_to_pending_construction(*it)) - valid_building_sites.push_back(*it); - else - open_air_sites.push_back(*it); + ok_to_continue = true; } - } - - return valid_building_sites.size() > 0; - } - - void apply_box_selection(bool new_start) - { - static bool saved_revert_setting = false; - static bool auto_select_applied = false; + if (in_future_placement_mode) + { + ok_to_continue = find_anchor_in_spiral(valid_building_sites[0].pos); + } + else if (ok_to_continue) + { + // First valid site is guaranteed to be anchored, either on a tile or against a valid orthogonal tile + // Use it as an anchor point to generate materials list + anchor = valid_building_sites.front(); + valid_building_sites.pop_front(); + valid_building_sites.push_back(anchor); + } - box_select_mode = SELECT_MATERIALS; - if (new_start) - { - if (!find_valid_building_sites()) + if (!ok_to_continue) { cancel_box_selection(); hollow_selection = false; @@ -796,11 +899,6 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest auto_select_applied = false; box_select_materials.clear(); - // First valid site is guaranteed to be anchored, either on a tile or against a valid orthogonal tile - // Use it as an anchor point to generate materials list - anchor = valid_building_sites.front(); - valid_building_sites.pop_front(); - valid_building_sites.push_back(anchor); } while (valid_building_sites.size() > 0) @@ -924,7 +1022,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest if (!last_used_moved && ui_build_selector->is_grouped) { last_used_moved = true; - if (choose_materials()) + if (!box_select_enabled && choose_materials()) { return; } @@ -990,40 +1088,36 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest OutputHotkeyString(x, y, autoselect_toggle_string.c_str(), "a", true, left_margin); OutputHotkeyString(x, y, revert_toggle_string.c_str(), "t", true, left_margin); - if (ui_build_selector->building_subtype == construction_type::Wall || - ui_build_selector->building_subtype == construction_type::Fortification || - ui_build_selector->building_subtype == construction_type::Floor) + ++y; + OutputHotkeyString(x, y, (box_select_enabled) ? "Disable Box Select" : "Enable Box Select", "b", true, left_margin); + if (box_select_enabled) { - ++y; - OutputHotkeyString(x, y, (box_select_enabled) ? "Disable Box Select" : "Enable Box Select", "b", true, left_margin); - if (box_select_enabled) - { - OutputHotkeyString(x, y, (show_box_selection) ? "Disable Box Marking" : "Enable Box Marking", "x", true, left_margin); - OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); - } - ++y; - if (box_select_enabled) - { - Screen::Pen pen(' ',COLOR_BLACK); - y = dims.y1 + 2; - Screen::fillRect(pen, x, y, dims.menu_x2, y + 17); - - y += 2; - switch (box_select_mode) - { - case SELECT_FIRST: - OutputString(COLOR_BROWN, x, y, "Choose first corner", true, left_margin); - break; + OutputHotkeyString(x, y, (show_box_selection) ? "Disable Box Marking" : "Enable Box Marking", "x", true, left_margin); + OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); + OutputHotkeyString(x, y, (allow_future_placement) ? "Disable Open Placement" : "Enable Open Placement", "o", true, left_margin); + } + ++y; + if (box_select_enabled) + { + Screen::Pen pen(' ',COLOR_BLACK); + y = dims.y1 + 2; + Screen::fillRect(pen, x, y, dims.menu_x2, y + 17); - case SELECT_SECOND: - OutputString(COLOR_GREEN, x, y, "Choose second corner", true, left_margin); - int cx = box_first.x; - int cy = box_first.y; - OutputString(COLOR_BROWN, cx, cy, "X"); - } + y += 2; + switch (box_select_mode) + { + case SELECT_FIRST: + OutputString(COLOR_BROWN, x, y, "Choose first corner", true, left_margin); + break; - OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); + case SELECT_SECOND: + OutputString(COLOR_GREEN, x, y, "Choose second corner", true, left_margin); + int cx = box_first.x; + int cy = box_first.y; + OutputString(COLOR_BROWN, cx, cy, "X"); } + + OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); } } } From 5d04148aad62144fb328cc75f3d0b5e318d48ea8 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 27 Dec 2012 20:15:32 +1300 Subject: [PATCH 021/138] Make gcc happy --- plugins/automaterial.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 9f943774e..430b529f4 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -849,7 +849,8 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest void move_cursor(coord32_t &pos) { - move_cursor(df::coord((int16_t) pos.x, (int16_t) pos.y, (int16_t) pos.z)); + df::coord c((int16_t) pos.x, (int16_t) pos.y, (int16_t) pos.z); + move_cursor(c); } void apply_box_selection(bool new_start) From a1eadd0f08129e93d4211d3fc08a920fbef0ac7b Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 1 Jan 2013 15:45:52 +1300 Subject: [PATCH 022/138] Allocate sky blocks when needed. Allow stairs to be designated on top of each other in open space. --- plugins/automaterial.cpp | 80 ++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 9f943774e..773beb708 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -318,33 +318,51 @@ static bool is_orthogonal_to_pending_construction(building_site &site) return false; } -static bool is_valid_building_site(building_site &site, bool orthogonal_check, bool check_placed_constructions, bool in_future_placement_mode) +static df::building_constructionst *get_construction_on_tile(const df::coord &pos) { - if (!Maps::isValidTilePos(site.pos)) - return false; + auto current = Buildings::findAtTile(pos); + if (current) + return strict_virtual_cast(current); + + return NULL; +} + +static df::tiletype *read_tile_shapes(const df::coord &pos, df::tiletype_shape &shape, df::tiletype_shape_basic &shape_basic) +{ + if (!Maps::isValidTilePos(pos)) + return NULL; - auto ttype = Maps::getTileType(site.pos); + auto ttype = Maps::getTileType(pos); if (!ttype) - return false; + return NULL; - auto shape = tileShape(*ttype); - auto shapeBasic = tileShapeBasic(shape); + shape = tileShape(*ttype); + shape_basic = tileShapeBasic(shape); - if (shapeBasic == tiletype_shape_basic::Open) + return ttype; +} + +static bool is_valid_building_site(building_site &site, bool orthogonal_check, bool check_placed_constructions, bool in_future_placement_mode) +{ + df::tiletype_shape shape; + df::tiletype_shape_basic shape_basic; + + auto ttype = read_tile_shapes(site.pos, shape, shape_basic); + if (!ttype) + return false; + + if (shape_basic == tiletype_shape_basic::Open) { if (orthogonal_check) { + // Check if this is a valid tile to have a construction placed orthogonally to it if (!in_future_placement_mode) return false; - auto current = Buildings::findAtTile(site.pos); - if (current) + df::building_constructionst *cons = get_construction_on_tile(site.pos); + if (cons && cons == construction_type::Floor) { - auto cons = strict_virtual_cast(current); - if (!cons || cons->type != construction_type::Floor) - return false; - site.in_open_air = true; return true; } @@ -352,6 +370,35 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check, b return false; } + // Stairs can be placed in open space, if they can connect to other stairs + df::tiletype_shape shape_s; + df::tiletype_shape_basic shape_basic_s; + + if (ui_build_selector->building_subtype == construction_type::DownStair || + ui_build_selector->building_subtype == construction_type::UpDownStair) + { + df::coord below(site.pos.x, site.pos.y, site.pos.z - 1); + auto ttype_s = read_tile_shapes(below, shape_s, shape_basic_s); + if (ttype_s) + { + if (shape_s == tiletype_shape::STAIR_UP || shape_s == tiletype_shape::STAIR_UPDOWN) + return true; + } + } + + if (ui_build_selector->building_subtype == construction_type::UpStair || + ui_build_selector->building_subtype == construction_type::UpDownStair) + { + df::coord above(site.pos.x, site.pos.y, site.pos.z + 1); + auto ttype_s = read_tile_shapes(above, shape_s, shape_basic_s); + if (ttype_s) + { + if (shape_s == tiletype_shape::STAIR_DOWN || shape_s == tiletype_shape::STAIR_UPDOWN) + return true; + } + } + + // Check if there is a valid tile orthogonally adjacent bool valid_orthogonal_tile_found = false; df::coord orthagonal_pos; orthagonal_pos.z = site.pos.z; @@ -382,7 +429,7 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check, b } else if (orthogonal_check) { - if (shape != tiletype_shape::RAMP && shapeBasic != tiletype_shape_basic::Floor) + if (shape != tiletype_shape::RAMP && shape_basic != tiletype_shape_basic::Floor) return false; } else @@ -395,7 +442,7 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check, b } else { - if (shapeBasic != tiletype_shape_basic::Floor) + if (shape_basic != tiletype_shape_basic::Floor) return false; if (material == tiletype_material::CONSTRUCTION) @@ -544,6 +591,7 @@ static bool designate_new_construction(df::coord &pos, df::construction_type &ty vector items; items.push_back(item); + Maps::ensureTileBlock(pos); if (!Buildings::constructWithItems(newinst, items)) { From 581a8dd955e06296f29cf5f855d580f2c56eb5f0 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Wed, 2 Jan 2013 22:27:26 +1300 Subject: [PATCH 023/138] Prevent material selection cursor from going past its bounds --- plugins/workflow.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 8fa26d17f..5d8abc107 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1630,13 +1630,13 @@ namespace wf_ui static int get_left_margin() { - int left_margin = gps->dimx - 30; - int8_t a = *df::global::ui_menu_width; - int8_t b = *df::global::ui_area_map_width; - if ((a == 1 && b > 1) || (a == 2 && b == 2)) - left_margin -= 24; - - return left_margin; + int left_margin = gps->dimx - 30; + int8_t a = *df::global::ui_menu_width; + int8_t b = *df::global::ui_area_map_width; + if ((a == 1 && b > 1) || (a == 2 && b == 2)) + left_margin -= 24; + + return left_margin; } /* @@ -2498,10 +2498,12 @@ namespace wf_ui 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) { From 7b561f108b0a2a38973be33b643f4145032f6d3e Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 3 Jan 2013 22:01:01 +1300 Subject: [PATCH 024/138] Fix hotkey detection --- plugins/workflow.cpp | 142 +++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 5d8abc107..bb309fff9 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1679,98 +1679,99 @@ namespace wf_ui bool AdjustmentScreen::feed(set *input, ItemConstraint *cv, ProtectedJob *pj /* = NULL */) { - if ((edit_limit || edit_gap)) + if (input->count(interface_key::CUSTOM_T) && !edit_limit && !edit_gap) { - df::interface_key last_token = *input->rbegin(); - if (last_token == interface_key::STRING_A000) + if (!cv) { - // Backspace - if (edit_string.length() > 0) - { - edit_string.erase(edit_string.length()-1); - } - - return true; + // Add tracking + return false; } - if (edit_string.length() >= 6) - return true; - - if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A057) + // Remove tracking + if (pj) { - // Numeric character - edit_string += last_token - ascii_to_enum_offset; - } - else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) - { - if (input->count(interface_key::SELECT) && edit_string.length() > 0) - { - if (edit_limit) - cv->setGoalCount(atoi(edit_string.c_str())); - else - cv->setGoalGap(atoi(edit_string.c_str())); + for (vector::iterator it = pj->constraints.begin(); it < pj->constraints.end(); it++) + delete_constraint(*it); - onConstraintChanged(); - } - edit_string.clear(); - edit_limit = false; - edit_gap = false; + forget_job(color_ostream_proxy(Core::getInstance().getConsole()), pj); } - else if (last_token == interface_key::STRING_A000) + else { - // Backspace - if (edit_string.length() > 0) - { - edit_string.erase(edit_string.length()-1); - } + delete_constraint(cv); } + onConstraintChanged(); return true; } - else if (input->count(interface_key::CUSTOM_L)) - { - edit_string = int_to_string(cv->goalCount()); - edit_limit = true; - } - else if (input->count(interface_key::CUSTOM_G)) - { - edit_string = int_to_string(cv->goalGap()); - edit_gap = true; - } - else if (input->count(interface_key::CUSTOM_N)) - { - cv->setGoalByCount(!cv->goalByCount()); - onModeChanged(); - } - else if (input->count(interface_key::CUSTOM_T)) + + if (cv) { - if (cv) + if (edit_limit || edit_gap) { - // Remove tracking - if (pj) + df::interface_key last_token = *input->rbegin(); + if (last_token == interface_key::STRING_A000) { - for (vector::iterator it = pj->constraints.begin(); it < pj->constraints.end(); it++) - delete_constraint(*it); + // Backspace + if (edit_string.length() > 0) + { + edit_string.erase(edit_string.length()-1); + } - forget_job(color_ostream_proxy(Core::getInstance().getConsole()), pj); + return true; } - else - delete_constraint(cv); + + if (edit_string.length() >= 6) + return true; + + if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A057) + { + // Numeric character + edit_string += last_token - ascii_to_enum_offset; + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + if (input->count(interface_key::SELECT) && edit_string.length() > 0) + { + if (edit_limit) + cv->setGoalCount(atoi(edit_string.c_str())); + else + cv->setGoalGap(atoi(edit_string.c_str())); + + onConstraintChanged(); + } + edit_string.clear(); + edit_limit = false; + edit_gap = false; + } + else if (last_token == interface_key::STRING_A000) + { + // Backspace + if (edit_string.length() > 0) + { + edit_string.erase(edit_string.length()-1); + } + } + + return true; } - else + else if (input->count(interface_key::CUSTOM_L)) { - // Add tracking - return false; + edit_string = int_to_string(cv->goalCount()); + edit_limit = true; + } + else if (input->count(interface_key::CUSTOM_G)) + { + edit_string = int_to_string(cv->goalGap()); + edit_gap = true; + } + else if (input->count(interface_key::CUSTOM_N)) + { + cv->setGoalByCount(!cv->goalByCount()); + onModeChanged(); } - - onConstraintChanged(); - } - else - { - return false; } - return true; + return false; } void AdjustmentScreen::render(ItemConstraint *cv, bool in_monitor) @@ -2934,7 +2935,6 @@ namespace wf_ui last_job = job; } - return job != NULL; } From 0fa27d6c34a7d1e536c5a4a3a87d621bc5bf35fb Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 14:59:39 +1300 Subject: [PATCH 025/138] Fix for gcc errors. It seems calls to base class members in a templated class must be fully template qualified. --- plugins/search.cpp | 50 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index d3983f45b..501c45ad1 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -376,10 +376,10 @@ protected: auto list = getLayerList(screen); if (!list->active) { - if (is_valid()) + if (this->is_valid()) { - clear_search(); - reset_all(); + this->clear_search(); + this->reset_all(); } return false; @@ -388,24 +388,24 @@ protected: return true; } - virtual void do_search() + virtual void do_search() { - search_generic::do_search(); - auto list = getLayerList(viewscreen); - list->num_entries = get_primary_list()->size(); + search_generic::do_search(); + auto list = getLayerList(this->viewscreen); + list->num_entries = this->get_primary_list()->size(); } - - int32_t *get_viewscreen_cursor() + + int32_t *get_viewscreen_cursor() { - auto list = getLayerList(viewscreen); + auto list = getLayerList(this->viewscreen); return &list->cursor; } virtual void clear_search() { - search_generic::clear_search(); - auto list = getLayerList(viewscreen); - list->num_entries = get_primary_list()->size(); + search_generic::clear_search(); + auto list = getLayerList(this->viewscreen); + list->num_entries = this->get_primary_list()->size(); } private: @@ -458,7 +458,7 @@ protected: virtual void clear_search() { - if (saved_list1.size() > 0) + if (this->saved_list1.size() > 0) { do_pre_incremental_search(); restore_secondary_values(); @@ -471,24 +471,24 @@ protected: virtual bool is_match(T &a, T &b) = 0; virtual bool is_match(vector &a, vector &b) = 0; - + void do_pre_incremental_search() { PARENT::do_pre_incremental_search(); if (read_only) return; - bool list_has_been_sorted = (primary_list->size() == reference_list.size() - && !is_match(*primary_list, reference_list)); + bool list_has_been_sorted = (this->primary_list->size() == reference_list.size() + && !is_match(*this->primary_list, reference_list)); for (size_t i = 0; i < saved_indexes.size(); i++) { int adjusted_item_index = i; if (list_has_been_sorted) { - for (size_t j = 0; j < primary_list->size(); j++) + for (size_t j = 0; j < this->primary_list->size(); j++) { - if (is_match((*primary_list)[j], reference_list[i])) + if (is_match((*this->primary_list)[j], reference_list[i])) { adjusted_item_index = j; break; @@ -503,14 +503,14 @@ protected: void clear_viewscreen_vectors() { - search_generic::clear_viewscreen_vectors(); + search_generic::clear_viewscreen_vectors(); saved_indexes.clear(); clear_secondary_viewscreen_vectors(); } void add_to_filtered_list(size_t i) { - search_generic::add_to_filtered_list(i); + search_generic::add_to_filtered_list(i); add_to_filtered_secondary_lists(i); if (!read_only) saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed @@ -519,12 +519,12 @@ protected: virtual void do_post_search() { if (!read_only) - reference_list = *primary_list; + reference_list = *this->primary_list; } void save_original_values() { - search_generic::save_original_values(); + search_generic::save_original_values(); save_secondary_values(); } }; @@ -556,7 +556,7 @@ protected: virtual void do_post_init() { - search_multicolumn_modifiable::do_post_init(); + search_multicolumn_modifiable::do_post_init(); secondary_list = get_secondary_list(); } @@ -1609,4 +1609,4 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch } #undef IMPLEMENT_HOOKS -#undef SEARCH_HOOKS \ No newline at end of file +#undef SEARCH_HOOKS From 140f78c8a0b91e103bd17edad39274741ccf75a9 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 18:14:25 +1300 Subject: [PATCH 026/138] Ignore vermin in animals screen search --- plugins/search.cpp | 63 ++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 501c45ad1..dac627b68 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -26,7 +26,7 @@ #include "modules/Gui.h" #include "df/unit.h" #include "df/misc_trait_type.h" -#include "df/unit_misc_trait.h" +#include "df/unit_misc_trait.h" using std::set; using std::vector; @@ -291,6 +291,11 @@ protected: } + virtual bool is_valid_for_search(size_t index) + { + return true; + } + // The actual sort virtual void do_search() { @@ -311,6 +316,9 @@ protected: string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) { + if (!is_valid_for_search(i)) + continue; + T element = saved_list1[i]; string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) @@ -697,6 +705,11 @@ private: return viewscreen->mode == T_mode::List; } + bool is_valid_for_search(size_t i) + { + return is_vermin_s[i] == 0; + } + void save_secondary_values() { is_vermin_s = *is_vermin; @@ -887,24 +900,24 @@ private: static string get_non_work_description(df::unit *unit) { - for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - { - int i = (*p)->value; - return ".on break"; - } - } - - if (unit->profession == profession::BABY || - unit->profession == profession::CHILD || - unit->profession == profession::DRUNK) - { - return ""; - } - - if (ENUM_ATTR(profession, military, unit->profession)) - return ".military"; + for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + { + int i = (*p)->value; + return ".on break"; + } + } + + if (unit->profession == profession::BABY || + unit->profession == profession::CHILD || + unit->profession == profession::DRUNK) + { + return ""; + } + + if (ENUM_ATTR(profession, military, unit->profession)) + return ".military"; return ".idle.no job"; } @@ -1490,12 +1503,12 @@ public: void render() const { - auto dims = Gui::getDwarfmodeViewDims(); - int left_margin = dims.menu_x1 + 1; - int x = left_margin; - int y = 23; - - print_search_option(x, y); + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 23; + + print_search_option(x, y); } vector *get_primary_list() From 6d6d55a937bea8796b5205b8b7aa0eb68089b860 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 18:32:49 +1300 Subject: [PATCH 027/138] Restore accidentally removed priority of unit screen search hook's input check over manipulator plugin. --- plugins/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index dac627b68..8d0805947 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -969,8 +969,9 @@ private: } }; -IMPLEMENT_HOOKS(df::viewscreen_unitlistst, unitlist_search); - +typedef generic_search_hook unitlist_search_hook; +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search // From 83ef94774bb45bd159e2745e01c8f59ea87d787c Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 12 Jan 2013 23:32:30 +1300 Subject: [PATCH 028/138] A better format for toggle strings. --- plugins/automaterial.cpp | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index ea7fd7457..712565ac0 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -97,6 +97,16 @@ void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bo 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); +} + static string int_to_string(int i) { return static_cast( &(ostringstream() << i))->str(); @@ -1109,16 +1119,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); if (material.valid) { - string toggle_string = "Enable"; - string title = "Disabled"; - if (check_autoselect(material, false)) - { - toggle_string = "Disable"; - title = "Enabled"; - } - - OutputString(COLOR_BROWN, x, y, "DFHack Autoselect: " + title, true, left_margin); - OutputHotkeyString(x, y, toggle_string.c_str(), "a", true, left_margin); + OutputToggleString(x, y, "Autoselect", "a", check_autoselect(material, false), true, left_margin); if (box_select_mode == SELECT_MATERIALS) { @@ -1130,20 +1131,17 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest } else if (in_placement_stage() && ui_build_selector->building_subtype < 7) { - string autoselect_toggle_string = (auto_choose_materials) ? "Disable Auto Mat-select" : "Enable Auto Mat-select"; - string revert_toggle_string = (revert_to_last_used_type) ? "Disable Auto Type-select" : "Enable Auto Type-select"; - OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); - OutputHotkeyString(x, y, autoselect_toggle_string.c_str(), "a", true, left_margin); - OutputHotkeyString(x, y, revert_toggle_string.c_str(), "t", true, left_margin); + OutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); + OutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); ++y; - OutputHotkeyString(x, y, (box_select_enabled) ? "Disable Box Select" : "Enable Box Select", "b", true, left_margin); + OutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); if (box_select_enabled) { - OutputHotkeyString(x, y, (show_box_selection) ? "Disable Box Marking" : "Enable Box Marking", "x", true, left_margin); + OutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); - OutputHotkeyString(x, y, (allow_future_placement) ? "Disable Open Placement" : "Enable Open Placement", "o", true, left_margin); + OutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); } ++y; if (box_select_enabled) From 192baa5638f01564cca702ddc623d59484687061 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 7 Jan 2013 21:17:38 +1300 Subject: [PATCH 029/138] Building plan plugin: place furniture before it's built. Initial checkin. --- plugins/CMakeLists.txt | 1 + plugins/buildingplan.cpp | 1291 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 1292 insertions(+) create mode 100644 plugins/buildingplan.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 34fedd77e..2f3c82f4f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -129,6 +129,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(buildingplan buildingplan.cpp) endif() diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp new file mode 100644 index 000000000..8be1c682f --- /dev/null +++ b/plugins/buildingplan.cpp @@ -0,0 +1,1291 @@ +// 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; +} \ No newline at end of file From f5a0644b452edaf68411fb5cb7f76e0da3b76527 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 18 Jan 2013 23:29:08 +1300 Subject: [PATCH 030/138] Fix bad refactor --- plugins/workflow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index bb309fff9..5afc26689 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1205,7 +1205,7 @@ static void map_job_items(color_ostream &out) df::item_flags bad_flags; bad_flags.whole = 0; -#define F(left_margin) bad_flags.bits.left_margin = true; +#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); From 35f4984b5f130e3dc41a241a3586cb0599dd8f18 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 19 Jan 2013 11:05:41 +1300 Subject: [PATCH 031/138] Enable multi-processor compilation on MSVC --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14436dcff..13e7bcf71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,7 +113,7 @@ IF(UNIX) SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic") ELSEIF(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") ENDIF() # use shared libraries for protobuf From 62ee18ad06fb553f8b7a2b15d3c7df3fd9797e8f Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 19 Jan 2013 12:21:57 +1300 Subject: [PATCH 032/138] Disable optimization in MSVC RelWithDebugInfo to make debugging easier. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 13e7bcf71..5b6f2f5da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,7 @@ IF(UNIX) ELSEIF(MSVC) # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od") ENDIF() # use shared libraries for protobuf From f03636068886227a241017c7086169a04607d743 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 19 Jan 2013 19:20:37 +1300 Subject: [PATCH 033/138] Add filters for quality, material and decorations. --- plugins/buildingplan.cpp | 804 +++++++++++++++++++++++++-------------- 1 file changed, 515 insertions(+), 289 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 8be1c682f..09c99f4e2 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -43,6 +43,8 @@ #include "df/building_design.h" #include "df/buildings_other_id.h" #include "modules/World.h" +#include +#include "df/building.h" using std::map; using std::string; @@ -58,7 +60,7 @@ using df::global::world; using df::global::enabler; DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.2 +#define PLUGIN_VERSION 0.3 struct MaterialDescriptor { @@ -175,6 +177,18 @@ static string pad_string(string text, const int size, const bool front = true, c } } +template +static void for_each_(vector &v, Fn func) +{ + for_each(v.begin(), v.end(), func); +} + +template +static void transform_(vector &src, vector &dst, Fn func) +{ + transform(src.begin(), src.end(), back_inserter(dst), func); +} + #define MAX_MASK 10 #define MAX_MATERIAL 21 @@ -213,24 +227,24 @@ public: 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; + bool allow_search; ListColumn() { clear(); left_margin = 2; bottom_margin = 3; - search_margin = 38; + search_margin = 63; highlighted_index = 0; multiselect = false; allow_null = true; auto_select = false; - search_entry_mode = false; force_sort = false; + allow_search = true; } void clear() @@ -279,22 +293,18 @@ public: display_extras(display_list[i]->elem, x, y); } - if (is_selected_column) + if (is_selected_column && allow_search) { 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, "_"); - } + OutputString(COLOR_WHITE, x, y, ": "); + OutputString(COLOR_WHITE, x, y, search_string); + OutputString(COLOR_LIGHTGREEN, x, y, "_"); } } - void filter_display() + void filterDisplay() { ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; display_list.clear(); @@ -400,11 +410,11 @@ public: vector getSelectedElems(bool only_one = false) { vector results; - for (typename vector< ListEntry >::iterator it = list.begin(); it != list.end(); it++) + for (auto it = list.begin(); it != list.end(); it++) { if ((*it).selected) { - results.push_back(&(*it).elem); + results.push_back(&(it->elem)); if (only_one) break; } @@ -413,6 +423,11 @@ public: return results; } + void clear_selection() + { + for_each_(list, [] (ListEntry &e) { e.selected = false; }); + } + T* getFirstSelectedElem() { vector results = getSelectedElems(true); @@ -436,34 +451,43 @@ public: { 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) + else if (input->count(interface_key::SELECT) && !auto_select) + { + toggleHighlighted(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_S)) + { + search_string.clear(); + filterDisplay(); + } + else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) { - // Search query typing mode + return setHighlightByMouse(); + } + else + { + // Search query typing mode always on df::interface_key last_token = *input->rbegin(); - if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) + if (last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) { // Standard character search_string += last_token - ascii_to_enum_offset; - filter_display(); + filterDisplay(); } else if (last_token == interface_key::STRING_A000) { @@ -471,45 +495,17 @@ public: if (search_string.length() > 0) { search_string.erase(search_string.length()-1); - filter_display(); + filterDisplay(); } } - 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)) + else { - // 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; } @@ -530,28 +526,19 @@ public: 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&)) + void sort() { if (force_sort || list.size() < 100) - std::sort(list.begin(), list.end(), function); - - filter_display(); - } + std::sort(list.begin(), list.end(), + [] (ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; }); - virtual void sort() - { - doSort(&compareText); + filterDisplay(); } private: - vector< ListEntry > list; - vector< ListEntry* > display_list; + vector> list; + vector*> display_list; string search_string; int display_max_rows; int max_item_width; @@ -561,246 +548,406 @@ public: /* * Material Choice Screen */ -class ViewscreenChooseMaterial : public dfhack_viewscreen + +struct ItemFilter { -public: - static bool reset_list; + df::dfhack_material_category mat_mask; + vector materials; + df::item_quality min_quality; + bool decorated_only; - ViewscreenChooseMaterial(); - void feed(set *input); - void render(); + ItemFilter() : min_quality(item_quality::Ordinary), decorated_only(false), valid(true) + { } - std::string getFocusString() { return "buildingplan_choosemat"; } + bool matchesMask(MaterialInfo &mat) + { + return (mat_mask.whole) ? mat.matches(mat_mask) : true; + } -private: - ListColumn masks_column; - ListColumn materials_column; - vector< ListEntry > all_masks; - int selected_column; + bool matches(const df::dfhack_material_category mask) const + { + return mask.whole & mat_mask.whole; + } - df::building_type btype; + bool matches(MaterialInfo &material) const + { + return any_of(materials.begin(), materials.end(), + [&] (const MaterialInfo &m) { return material.matches(m); }); + } + + bool matches(df::item *item) + { + if (item->getQuality() < min_quality) + return false; - void populateMasks(const bool set_defaults = false); - void populateMaterials(const bool set_defaults = false); + if (decorated_only && !item->hasImprovements()) + return false; - bool addMaterialEntry(df::dfhack_material_category &selected_category, - MaterialInfo &material, string name, const bool set_defaults); + auto imattype = item->getActualMaterial(); + auto imatindex = item->getActualMaterialIndex(); + auto item_mat = MaterialInfo(imattype, imatindex); - virtual void resize(int32_t x, int32_t y); + return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat); + } - void validateColumn(); -}; + vector getMaterialFilterAsVector() + { + vector descriptions; -bool ViewscreenChooseMaterial::reset_list = false; + transform_(materials, descriptions, + [] (MaterialInfo m) { return m.toString(); }); + if (descriptions.size() == 0) + bitfield_to_string(&descriptions, mat_mask); -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; + if (descriptions.size() == 0) + descriptions.push_back("any"); - all_masks.push_back(ListEntry(pad_string(raw_masks[i], MAX_MASK, false), curr_mat_mask)); - curr_mat_mask.whole <<= 1; + return descriptions; } - 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++) + string getMaterialFilterAsSerial() { - auto entry = *it; - if (set_defaults) + string str; + + str.append(bitfield_to_string(mat_mask, ",")); + str.append("/"); + if (materials.size() > 0) { - //TODO - //if (cv->mat_mask.whole & entry.elem.whole) - //entry.selected = true; + for_each_(materials, + [&] (MaterialInfo &m) { str.append(m.getToken() + ","); }); + + if (str[str.size()-1] == ',') + str.resize(str.size () - 1); } - masks_column.add(entry); + + return str; } - 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) + + bool parseSerializedMaterialTokens(string str) + { + valid = false; + vector tokens; + split_string(&tokens, str, "/"); + + if (tokens.size() > 0 && !tokens[0].empty()) { - MaterialInfo material; - material.decode(i, -1); - addMaterialEntry(selected_category, material, material.toString(), set_defaults); + if (!parseJobMaterialCategory(&mat_mask, tokens[0])) + return false; } + + if (tokens.size() > 1 && !tokens[1].empty()) + { + vector mat_names; + split_string(&mat_names, tokens[1], ","); + for (auto m = mat_names.begin(); m != mat_names.end(); m++) + { + MaterialInfo material; + if (!material.find(*m) || !material.isValid()) + return false; + + materials.push_back(material); + } + } + + valid = true; + return true; } - for (size_t i = 0; i < raws.inorganics.size(); i++) + string getMinQuality() { - df::inorganic_raw *p = raws.inorganics[i]; - MaterialInfo material; - material.decode(0, i); - addMaterialEntry(selected_category, material, material.toString(), set_defaults); + return ENUM_KEY_STR(item_quality, min_quality); } - 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)) + bool isValid() { - 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 valid; } - return selected; -} +private: + bool valid; +}; + -void ViewscreenChooseMaterial::feed(set *input) +class ViewscreenChooseMaterial : public dfhack_viewscreen { - bool key_processed; - switch (selected_column) +public: + ViewscreenChooseMaterial(ItemFilter *filter) { - 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; - } + selected_column = 0; + masks_column.title = "Type"; + masks_column.multiselect = true; + masks_column.allow_search = false; + masks_column.left_margin = 2; + materials_column.left_margin = MAX_MASK + 3; + materials_column.title = "Material"; + materials_column.multiselect = true; + this->filter = filter; - if (key_processed) - return; + masks_column.changeHighlight(0); - if (input->count(interface_key::LEAVESCREEN)) - { - input->clear(); - Screen::dismiss(this); - return; + populateMasks(); + populateMaterials(); + + masks_column.selectDefaultEntry(); + materials_column.selectDefaultEntry(); + materials_column.changeHighlight(0); } - else if (input->count(interface_key::SEC_SELECT)) + + void feed(set *input) { - df::dfhack_material_category mat_mask; - vector selected_masks = masks_column.getSelectedElems(); - for (vector::iterator it = selected_masks.begin(); it != selected_masks.end(); it++) + bool key_processed; + 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)) { - mat_mask.whole |= (*it)->whole; + input->clear(); + Screen::dismiss(this); + return; + } + if (input->count(interface_key::CUSTOM_SHIFT_C)) + { + masks_column.clear_selection(); + materials_column.clear_selection(); } + else if (input->count(interface_key::SEC_SELECT)) + { + // Convert list selections to material filters - MaterialInfo *selected_material = materials_column.getFirstSelectedElem(); - MaterialInfo material = (selected_material) ? *selected_material : MaterialInfo(); - //TODO + filter->mat_mask.whole = 0; + filter->materials.clear(); + + // Category masks + for_each_(masks_column.getSelectedElems(), + [&] (df::dfhack_material_category *m) { filter->mat_mask.whole |= m->whole; }); + + // Specific materials + transform_(materials_column.getSelectedElems(), filter->materials, + [] (MaterialInfo *m) { return *m; }); + + 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; - reset_list = true; - Screen::dismiss(this); + enabler->mouse_lbut = enabler->mouse_rbut = 0; + } } - else if (input->count(interface_key::CURSOR_LEFT)) + + void render() { - --selected_column; - validateColumn(); + 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", "Enter"); + x += 3; + OutputHotkeyString(x, y, "Save", "Shift-Enter"); + x += 3; + OutputHotkeyString(x, y, "Clear", "C"); + x += 3; + OutputHotkeyString(x, y, "Cancel", "Esc"); } - else if (input->count(interface_key::CURSOR_RIGHT)) + + 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 string &text) { - selected_column++; - validateColumn(); + auto entry = ListEntry(pad_string(text, MAX_MASK, false), mask); + if (filter->matches(mask)) + entry.selected = true; + + masks_column.add(entry); + mask.whole = 0; } - else if (enabler->tracking_on && enabler->mouse_lbut) + + void populateMasks() { - if (masks_column.setHighlightByMouse()) - selected_column = 0; - else if (materials_column.setHighlightByMouse()) - selected_column = 1; + masks_column.clear(); + df::dfhack_material_category mask; - enabler->mouse_lbut = enabler->mouse_rbut = 0; + mask.bits.stone = true; + addMaskEntry(mask, "Stone"); + mask.bits.wood = true; + addMaskEntry(mask, "Wood"); + mask.bits.metal = true; + addMaskEntry(mask, "Metal"); + mask.bits.soap = true; + addMaskEntry(mask, "Soap"); + + masks_column.filterDisplay(); } -} + + void populateMaterials() + { + materials_column.clear(); + df::dfhack_material_category selected_category; + vector selected_masks = masks_column.getSelectedElems(); + if (selected_masks.size() == 1) + selected_category = *selected_masks[0]; + else if (selected_masks.size() > 1) + return; -void ViewscreenChooseMaterial::render() -{ - if (Screen::isDismissed(this)) - 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()); + } + } - dfhack_viewscreen::render(); + 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()); + } - Screen::clear(); - Screen::drawBorder(" Building Material "); + 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]; + //string basename = p->name; - masks_column.display(selected_column == 0); - materials_column.display(selected_column == 1); + /*MaterialInfo material; + material.decode(p->material_defs.type_basic_mat, p->material_defs.idx_basic_mat);*/ - 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); -} + //addMaterialEntry(selected_category, material, material.toString()); -void ViewscreenChooseMaterial::resize(int32_t x, int32_t y) -{ - dfhack_viewscreen::resize(x, y); - masks_column.resize(); - materials_column.resize(); -} + 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); + //addMaterialEntry(selected_category, material, material.toString()); + 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, 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(); + } +}; // START Planning class PlannedBuilding { public: - PlannedBuilding(df::building *building) : min_quality(item_quality::Ordinary) + PlannedBuilding(df::building *building, ItemFilter *filter) { this->building = building; + this->filter = *filter; pos = df::coord(building->centerx, building->centery, building->z); + config = Core::getInstance().getWorld()->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; + } + + 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)); + pos = df::coord(building->centerx, building->centery, building->z); + filter.min_quality = static_cast(config.ival(2) - 1); + filter.decorated_only = config.ival(3) - 1; } df::building_type getType() @@ -814,7 +961,11 @@ public: 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 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; @@ -873,22 +1024,36 @@ public: bool isValid() { - return building && Buildings::findAtTile(pos) == building; + bool valid = filter.isValid() && + building && Buildings::findAtTile(pos) == building && + building->getBuildStage() == 0; + + if (!valid) + remove(); + + return valid; } bool isCurrentlySelectedBuilding() { - return building == world->selected_building; + return isValid() && (building == world->selected_building); + } + + ItemFilter *getFilter() + { + return &filter; + } + + void remove() + { + Core::getInstance().getWorld()->DeletePersistentData(config); } private: df::building *building; PersistentDataItem config; df::coord pos; - - vector materials; - df::dfhack_material_category mat_mask; - item_quality::item_quality min_quality; + ItemFilter filter; }; @@ -900,29 +1065,18 @@ public: return item_for_building_type.find(type) != item_for_building_type.end(); } - void reset() + void reset(color_ostream &out) { 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; + auto pworld = Core::getInstance().getWorld(); + std::vector items; + pworld->GetPersistentData(&items, "buildingplan/constraints"); - addPlannedBuilding(bld); - } + for (auto i = items.begin(); i != items.end(); i++) + { + PlannedBuilding pb(*i, out); + if (pb.isValid()) + planned_buildings.push_back(pb); } } @@ -958,9 +1112,13 @@ public: { 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; + auto btype = (df::building_type) (i-1); + auto itype = (df::item_type) j; + + item_for_building_type[btype] = itype; + default_item_filters[btype] = ItemFilter(); + available_item_vectors[itype] = vector(); + is_relevant_item_type[itype] = true; } } } @@ -968,7 +1126,7 @@ public: void addPlannedBuilding(df::building *bld) { - PlannedBuilding pb(bld); + PlannedBuilding pb(bld, &default_item_filters[bld->getType()]); planned_buildings.push_back(pb); } @@ -1028,23 +1186,43 @@ public: return true; } - bool canUnsuspendSelectedBuilding() + PlannedBuilding *getSelectedPlannedBuilding() { for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++) { - if (building_iter->isValid() && building_iter->isCurrentlySelectedBuilding()) + if (building_iter->isCurrentlySelectedBuilding()) { - return false; + return &(*building_iter); } } - return true; + return nullptr; } + void removeSelectedPlannedBuilding() + { + getSelectedPlannedBuilding()->remove(); + } + + ItemFilter *getDefaultItemFilterForType(df::building_type type) + { + return &default_item_filters[type]; + } + + void cycleDefaultQuality(df::building_type type) + { + auto quality = &getDefaultItemFilterForType(type)->min_quality; + *quality = static_cast(*quality + 1); + if (*quality == item_quality::Artifact) + (*quality) = item_quality::Ordinary; + } + + private: map item_for_building_type; + map default_item_filters; map> available_item_vectors; - map is_relevant_item_type; //Needed for fast check when loopin over all items + map is_relevant_item_type; //Needed for fast check when looping over all items vector planned_buildings; @@ -1081,6 +1259,9 @@ private: if (itype == item_type::BOX && item->isBag()) continue; //Skip bags + if (item->flags.bits.artifact1 || item->flags.bits.artifact2) + continue; + if (item->flags.bits.in_job || item->isAssignedToStockpile() || item->flags.bits.owned || @@ -1138,7 +1319,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest { return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding || ui->main.mode == df::ui_sidebar_mode::BuildingItems) && - !planner.canUnsuspendSelectedBuilding(); + planner.getSelectedPlannedBuilding(); } bool isInPlannedBuildingPlacementMode() @@ -1149,7 +1330,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest planner.isPlanableBuilding(ui_build_selector->building_type); } - bool handle_input(set *input) + bool handleInput(set *input) { if (isInPlannedBuildingPlacementMode()) { @@ -1177,12 +1358,32 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest return true; } + else if (input->count(interface_key::CUSTOM_M)) + { + Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type))); + } + else if (input->count(interface_key::CUSTOM_Q)) + { + planner.cycleDefaultQuality(type); + } + else if (input->count(interface_key::CUSTOM_D)) + { + planner.getDefaultItemFilterForType(type)->decorated_only = + !planner.getDefaultItemFilterForType(type)->decorated_only; + } } } - else if (isInPlannedBuildingQueryMode() && - input->count(interface_key::SUSPENDBUILDING)) + else if (isInPlannedBuildingQueryMode()) { - return true; // Don't unsuspend planned buildings + 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 + } + } return false; @@ -1190,7 +1391,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - if (!handle_input(input)) + if (!handleInput(input)) INTERPOSE_NEXT(feed)(input); } @@ -1225,15 +1426,28 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest auto dims = Gui::getDwarfmodeViewDims(); int left_margin = dims.menu_x1 + 1; int x = left_margin; + auto type = ui_build_selector->building_type; if (plannable) { int y = 23; - OutputToggleString(x, y, "Planning Mode", "p", is_planmode_enabled(ui_build_selector->building_type), true, left_margin); + OutputToggleString(x, y, "Planning Mode", "p", is_planmode_enabled(type), true, left_margin); - if (is_planmode_enabled(ui_build_selector->building_type)) + if (is_planmode_enabled(type)) { - //OutputHotkeyString(x, y, "Material Filter", "m", true, left_margin); + auto filter = planner.getDefaultItemFilterForType(type); + + OutputHotkeyString(x, y, "Min Quality: ", "q"); + OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); + + OutputHotkeyString(x, y, "Decorated Only: ", "d"); + OutputString(COLOR_BROWN, x, y, + (filter->decorated_only) ? "Yes" : "No", true, left_margin); + + OutputHotkeyString(x, y, "Material Filter:", "m", true, left_margin); + auto filter_descriptions = filter->getMaterialFilterAsVector(); + for_each_(filter_descriptions, + [&](string d) { OutputString(COLOR_BROWN, x, y, " *" + d, true, left_margin); }); } } else if (isInPlannedBuildingQueryMode()) @@ -1242,6 +1456,18 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest 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_BLUE, x, y, filter->getMinQuality(), 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); + for_each_(filter->getMaterialFilterAsVector(), + [&](string d) { OutputString(COLOR_BLUE, x, y, "*" + d, true, left_margin); }); } } }; @@ -1281,7 +1507,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { switch (event) { case SC_MAP_LOADED: - planner.reset(); + planner.reset(out); break; default: break; From 6546af94ee6e0225d39b7426d6de403cdd1cb096 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 24 Jan 2013 19:21:12 +1300 Subject: [PATCH 034/138] Clear persistent data after item assigned. Fix some material selection screen functionality. --- plugins/buildingplan.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 09c99f4e2..aaab1c63f 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -478,7 +478,7 @@ public: { return setHighlightByMouse(); } - else + else if (allow_search) { // Search query typing mode always on @@ -505,6 +505,10 @@ public: return true; } + else + { + return false; + } return true; } @@ -664,6 +668,12 @@ struct ItemFilter return valid; } + void clear() + { + mat_mask.whole = 0; + materials.clear(); + } + private: bool valid; }; @@ -720,8 +730,10 @@ public: } if (input->count(interface_key::CUSTOM_SHIFT_C)) { + filter->clear(); masks_column.clear_selection(); materials_column.clear_selection(); + populateMaterials(); } else if (input->count(interface_key::SEC_SELECT)) { @@ -980,6 +992,7 @@ public: if (closest_distance > -1 && assignItem(*closest_item)) { items_vector->erase(closest_item); + remove(); return true; } From ae8bb7c9f87360f92ab71fc6b1af3e13d45d5746 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 29 Jan 2013 20:44:56 +1300 Subject: [PATCH 035/138] Fix for gcc errors. --- plugins/buildingplan.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index aaab1c63f..b8015de1e 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -744,11 +744,13 @@ public: filter->materials.clear(); // Category masks - for_each_(masks_column.getSelectedElems(), + auto masks = masks_column.getSelectedElems(); + for_each_(masks, [&] (df::dfhack_material_category *m) { filter->mat_mask.whole |= m->whole; }); // Specific materials - transform_(materials_column.getSelectedElems(), filter->materials, + auto materials = materials_column.getSelectedElems(); + transform_(materials, filter->materials, [] (MaterialInfo *m) { return *m; }); Screen::dismiss(this); @@ -1477,9 +1479,10 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest if (filter->decorated_only) OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin); - + OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin); - for_each_(filter->getMaterialFilterAsVector(), + auto filters = filter->getMaterialFilterAsVector(); + for_each_(filters, [&](string d) { OutputString(COLOR_BLUE, x, y, "*" + d, true, left_margin); }); } } @@ -1527,4 +1530,4 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } return CR_OK; -} \ No newline at end of file +} From 056bde451a65dd570dc08c1fab75b3859ddf7f09 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 29 Jan 2013 21:22:17 +1300 Subject: [PATCH 036/138] Remove some leftover code --- plugins/buildingplan.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index b8015de1e..24dbc4f33 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,5 +1,3 @@ -// Auto Material Select - #include #include #include @@ -80,11 +78,6 @@ struct MaterialDescriptor } }; -static command_result automaterial_cmd(color_ostream &out, vector & parameters) -{ - return CR_OK; -} - DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; From ecf255243f9feaf18fd8aacfd670a1f717889085 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Wed, 30 Jan 2013 21:49:46 +1300 Subject: [PATCH 037/138] Plugin to easily find and resume suspended constructions --- plugins/CMakeLists.txt | 1 + plugins/resume.cpp | 316 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 317 insertions(+) create mode 100644 plugins/resume.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2f3c82f4f..e1410116b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,6 +130,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(buildingplan buildingplan.cpp) + DFHACK_PLUGIN(resume resume.cpp) endif() diff --git a/plugins/resume.cpp b/plugins/resume.cpp new file mode 100644 index 000000000..8920b9ce5 --- /dev/null +++ b/plugins/resume.cpp @@ -0,0 +1,316 @@ +#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/viewscreen_dwarfmodest.h" +#include "df/world.h" +#include "df/building_constructionst.h" +#include "df/building.h" +#include "df/job.h" +#include "df/job_item.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "modules/Buildings.h" +#include "modules/Maps.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::world; + +DFHACK_PLUGIN("resume"); +#define PLUGIN_VERSION 0.1 + +#ifndef HAVE_NULLPTR +#define nullptr 0L +#endif + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +template +static void for_each_(vector &v, Fn func) +{ + for_each(v.begin(), v.end(), func); +} + +template +static void transform_(vector &src, vector &dst, Fn func) +{ + transform(src.begin(), src.end(), back_inserter(dst), func); +} + +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(); +} + +df::job *get_suspended_job(df::building *bld) +{ + if (bld->getBuildStage() != 0) + return nullptr; + + if (bld->jobs.size() == 0) + return nullptr; + + auto job = bld->jobs[0]; + if (job->flags.bits.suspend) + return job; + + return nullptr; +} + +struct SuspendedBuilding +{ + df::building *bld; + df::coord pos; + bool was_resumed; + bool is_planned; + + SuspendedBuilding(df::building *bld_) : bld(bld_), was_resumed(false), is_planned(false) + { + pos = df::coord(bld->centerx, bld->centery, bld->z); + } + + bool isValid() + { + return bld && Buildings::findAtTile(pos) == bld && get_suspended_job(bld); + } +}; + +static bool enabled = false; +static bool buildings_scanned = false; +static vector suspended_buildings, resumed_buildings; + +void scan_for_suspended_buildings() +{ + if (buildings_scanned) + return; + + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) + { + auto bld = *b; + auto job = get_suspended_job(bld); + if (job) + { + SuspendedBuilding sb(bld); + sb.is_planned = job->job_items.size() == 1 && job->job_items[0]->item_type == item_type::NONE; + + auto it = find_if(resumed_buildings.begin(), resumed_buildings.end(), + [&] (SuspendedBuilding &rsb) { return rsb.bld == bld; }); + + sb.was_resumed = it != resumed_buildings.end(); + + suspended_buildings.push_back(sb); + } + } + + buildings_scanned = true; +} + +void show_suspended_buildings() +{ + int32_t vx, vy, vz; + if (!Gui::getViewCoords(vx, vy, vz)) + return; + + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = vx + dims.map_x2; + int bottom_margin = vy + dims.y2; + + for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end();) + { + if (!sb->isValid()) + { + sb = suspended_buildings.erase(sb); + continue; + } + + if (sb->bld->z == vz && sb->bld->centerx >= vx && sb->bld->centerx <= left_margin && + sb->bld->centery >= vy && sb->bld->centery <= bottom_margin) + { + int x = sb->bld->centerx - vx + 1; + int y = sb->bld->centery - vy + 1; + auto color = COLOR_YELLOW; + if (sb->is_planned) + color = COLOR_GREEN; + else if (sb->was_resumed) + color = COLOR_RED; + + OutputString(color, x, y, "X"); + } + + sb++; + } +} + +void clear_scanned() +{ + buildings_scanned = false; + suspended_buildings.clear(); +} + +void resume_suspended_buildings(color_ostream &out) +{ + out << "Resuming all buildings." << endl; + + for (auto isb = resumed_buildings.begin(); isb != resumed_buildings.end();) + { + if (isb->isValid()) + { + isb++; + continue; + } + + isb = resumed_buildings.erase(isb); + } + + scan_for_suspended_buildings(); + for (auto sb = suspended_buildings.begin(); sb != suspended_buildings.end(); sb++) + { + if (sb->is_planned) + continue; + + resumed_buildings.push_back(*sb); + sb->bld->jobs[0]->flags.bits.suspend = false; + } + + clear_scanned(); + + out << resumed_buildings.size() << " buildings resumed" << endl; +} + + +//START Viewscreen Hook +struct resume_hook : public df::viewscreen_dwarfmodest +{ + //START UI Methods + typedef df::viewscreen_dwarfmodest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + DFHack::World *world = Core::getInstance().getWorld(); + if (enabled && world->ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default) + { + scan_for_suspended_buildings(); + show_suspended_buildings(); + } + else + { + clear_scanned(); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(resume_hook, render); + + +static command_result resume_cmd(color_ostream &out, vector & parameters) +{ + bool show_help = false; + if (parameters.empty()) + { + show_help = true; + } + else + { + auto cmd = parameters[0][0]; + if (cmd == 'v') + { + out << "Resume" << endl << "Version: " << PLUGIN_VERSION << endl; + } + else if (cmd == 's') + { + enabled = true; + out << "Overlay enabled" << endl; + } + else if (cmd == 'h') + { + enabled = false; + out << "Overlay disabled" << endl; + } + else if (cmd == 'a') + { + resume_suspended_buildings(out); + } + else + { + show_help = true; + } + } + + if (show_help) + return CR_WRONG_USAGE; + + return CR_OK; +} + + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (!gps || !INTERPOSE_HOOK(resume_hook, render).apply()) + out.printerr("Could not insert resume hooks!\n"); + + commands.push_back( + PluginCommand( + "resume", "A plugin to help display and resume suspended constructions conveniently", + resume_cmd, false, + "resume show\n" + " Show overlay when paused:\n" + " Yellow: Suspended construction\n" + " Red: Suspended after resume attempt, possibly stuck\n" + " Green: Planned building waiting for materials\n" + "resume hide\n" + " Hide overlay\n" + "resume all\n" + " Resume all suspended building constructions\n" + )); + + return CR_OK; +} + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + suspended_buildings.clear(); + resumed_buildings.clear(); + break; + default: + break; + } + + return CR_OK; +} From dff16c3c1dc654d1ccb4628cce6e1105c14bbd2a Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 2 Feb 2013 02:22:06 +1300 Subject: [PATCH 038/138] Define nullptr for gcc < 4.6 --- plugins/buildingplan.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 24dbc4f33..cf512ebf0 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -58,7 +58,11 @@ using df::global::world; using df::global::enabler; DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.3 +#define PLUGIN_VERSION 0.4 + +#ifndef HAVE_NULLPTR +#define nullptr 0L +#endif struct MaterialDescriptor { From 1956f8b92d83caaf3a3803923d0eb377e673881f Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 7 Feb 2013 22:57:07 +1300 Subject: [PATCH 039/138] Lock selection mode toggle --- plugins/buildingplan.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index cf512ebf0..3376adc1f 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -58,7 +58,7 @@ using df::global::world; using df::global::enabler; DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.4 +#define PLUGIN_VERSION 0.5 #ifndef HAVE_NULLPTR #define nullptr 0L @@ -1072,6 +1072,13 @@ private: class Planner { public: + bool lock_selection; + + Planner() : lock_selection(true) + { + + } + bool isPlanableBuilding(const df::building_type type) const { return item_for_building_type.find(type) != item_for_building_type.end(); @@ -1366,10 +1373,16 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); + if (!planner.lock_selection) + send_key(interface_key::LEAVESCREEN); } return true; } + else if (input->count(interface_key::CUSTOM_L)) + { + planner.lock_selection = !planner.lock_selection; + } else if (input->count(interface_key::CUSTOM_M)) { Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type))); @@ -1447,6 +1460,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest if (is_planmode_enabled(type)) { + OutputToggleString(x, y, "Lock Selection", "l", planner.lock_selection, true, left_margin); + auto filter = planner.getDefaultItemFilterForType(type); OutputHotkeyString(x, y, "Min Quality: ", "q"); From 2f4979d3fad212b2e4fae4736cb976aad0235a56 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 8 Feb 2013 00:04:52 +1300 Subject: [PATCH 040/138] Add Quickfort mode --- plugins/buildingplan.cpp | 81 +++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 3376adc1f..b576d41b9 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1072,9 +1072,9 @@ private: class Planner { public: - bool lock_selection; + bool quickfort_mode, in_dummmy_screen; - Planner() : lock_selection(true) + Planner() : quickfort_mode(false), in_dummmy_screen(false) { } @@ -1361,27 +1361,42 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); + planner.in_dummmy_screen = false; } return true; } if (is_planmode_enabled(type)) { + if (planner.quickfort_mode && planner.in_dummmy_screen) + { + if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) + || input->count(interface_key::LEAVESCREEN)) + { + planner.in_dummmy_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)) { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); - if (!planner.lock_selection) - send_key(interface_key::LEAVESCREEN); + if (planner.quickfort_mode) + { + planner.in_dummmy_screen = true; + } } return true; } - else if (input->count(interface_key::CUSTOM_L)) + else if (input->count(interface_key::CUSTOM_F)) { - planner.lock_selection = !planner.lock_selection; + planner.quickfort_mode = !planner.quickfort_mode; } else if (input->count(interface_key::CUSTOM_M)) { @@ -1454,31 +1469,51 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest auto type = ui_build_selector->building_type; if (plannable) { - int y = 23; + if (planner.quickfort_mode && planner.in_dummmy_screen) + { + Screen::Pen pen(' ',COLOR_BLACK); + int y = dims.y1 + 1; + Screen::fillRect(pen, x, y, dims.menu_x2, y + 20); - OutputToggleString(x, y, "Planning Mode", "p", is_planmode_enabled(type), true, left_margin); + ++y; - if (is_planmode_enabled(type)) + OutputString(COLOR_BROWN, x, y, "Quickfort Placeholder", true, left_margin); + OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); + } + else { - OutputToggleString(x, y, "Lock Selection", "l", planner.lock_selection, true, left_margin); + int y = 23; - auto filter = planner.getDefaultItemFilterForType(type); + OutputToggleString(x, y, "Planning Mode", "p", is_planmode_enabled(type), true, left_margin); - OutputHotkeyString(x, y, "Min Quality: ", "q"); - OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); + if (is_planmode_enabled(type)) + { + OutputToggleString(x, y, "Quickfort Mode", "f", planner.quickfort_mode, true, left_margin); - OutputHotkeyString(x, y, "Decorated Only: ", "d"); - OutputString(COLOR_BROWN, x, y, - (filter->decorated_only) ? "Yes" : "No", true, left_margin); - - OutputHotkeyString(x, y, "Material Filter:", "m", true, left_margin); - auto filter_descriptions = filter->getMaterialFilterAsVector(); - for_each_(filter_descriptions, - [&](string d) { OutputString(COLOR_BROWN, x, y, " *" + d, true, left_margin); }); + auto filter = planner.getDefaultItemFilterForType(type); + + OutputHotkeyString(x, y, "Min Quality: ", "q"); + OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); + + OutputHotkeyString(x, y, "Decorated Only: ", "d"); + OutputString(COLOR_BROWN, x, y, + (filter->decorated_only) ? "Yes" : "No", true, left_margin); + + OutputHotkeyString(x, y, "Material Filter:", "m", true, left_margin); + auto filter_descriptions = filter->getMaterialFilterAsVector(); + for_each_(filter_descriptions, + [&](string d) { OutputString(COLOR_BROWN, x, y, " *" + d, true, left_margin); }); + } + else + { + planner.in_dummmy_screen = false; + } } } else if (isInPlannedBuildingQueryMode()) { + planner.in_dummmy_screen = false; + // Hide suspend toggle option int y = 20; Screen::Pen pen(' ', COLOR_BLACK); @@ -1497,6 +1532,10 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest for_each_(filters, [&](string d) { OutputString(COLOR_BLUE, x, y, "*" + d, true, left_margin); }); } + else + { + planner.in_dummmy_screen = false; + } } }; From a5c21745eb2b4813716ecec430f97f43548a69e7 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 9 Feb 2013 16:49:36 +1300 Subject: [PATCH 041/138] Enable all plan modes when entering quickfort mode --- plugins/buildingplan.cpp | 55 ++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index b576d41b9..c3a1f3867 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -180,6 +180,12 @@ static void for_each_(vector &v, Fn func) for_each(v.begin(), v.end(), func); } +template +static void for_each_(map &v, Fn func) +{ + for_each(v.begin(), v.end(), func); +} + template static void transform_(vector &src, vector &dst, Fn func) { @@ -1069,10 +1075,12 @@ private: }; +static map planmode_enabled, saved_planmodes; + class Planner { public: - bool quickfort_mode, in_dummmy_screen; + bool in_dummmy_screen; Planner() : quickfort_mode(false), in_dummmy_screen(false) { @@ -1138,6 +1146,11 @@ public: default_item_filters[btype] = ItemFilter(); available_item_vectors[itype] = vector(); is_relevant_item_type[itype] = true; + + if (planmode_enabled.find(btype) == planmode_enabled.end()) + { + planmode_enabled[btype] = false; + } } } } @@ -1236,12 +1249,32 @@ public: (*quality) = item_quality::Ordinary; } + void enableQuickfortMode() + { + saved_planmodes = planmode_enabled; + for_each_(planmode_enabled, + [] (pair& pair) { pair.second = true; } ); + + 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; vector planned_buildings; @@ -1297,12 +1330,11 @@ private: 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 false; } return planmode_enabled[type]; @@ -1368,7 +1400,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest if (is_planmode_enabled(type)) { - if (planner.quickfort_mode && planner.in_dummmy_screen) + if (planner.inQuickFortMode() && planner.in_dummmy_screen) { if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) || input->count(interface_key::LEAVESCREEN)) @@ -1386,7 +1418,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest { send_key(interface_key::CURSOR_DOWN_Z); send_key(interface_key::CURSOR_UP_Z); - if (planner.quickfort_mode) + if (planner.inQuickFortMode()) { planner.in_dummmy_screen = true; } @@ -1396,7 +1428,14 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else if (input->count(interface_key::CUSTOM_F)) { - planner.quickfort_mode = !planner.quickfort_mode; + if (!planner.inQuickFortMode()) + { + planner.enableQuickfortMode(); + } + else + { + planner.disableQuickfortMode(); + } } else if (input->count(interface_key::CUSTOM_M)) { @@ -1469,7 +1508,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest auto type = ui_build_selector->building_type; if (plannable) { - if (planner.quickfort_mode && planner.in_dummmy_screen) + if (planner.inQuickFortMode() && planner.in_dummmy_screen) { Screen::Pen pen(' ',COLOR_BLACK); int y = dims.y1 + 1; @@ -1488,7 +1527,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest if (is_planmode_enabled(type)) { - OutputToggleString(x, y, "Quickfort Mode", "f", planner.quickfort_mode, true, left_margin); + OutputToggleString(x, y, "Quickfort Mode", "f", planner.inQuickFortMode(), true, left_margin); auto filter = planner.getDefaultItemFilterForType(type); From fe70df593b505f7f2c5337c5e0ea3ee1aecaff8e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 14 Feb 2013 18:36:21 -0600 Subject: [PATCH 042/138] Sync submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index c7e2c28fe..76fb647a4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c7e2c28febd6dca06ff7e9951090982fbbee12b5 +Subproject commit 76fb647a42ba2064d11f2a0be7bf04f6e3622bc5 diff --git a/plugins/stonesense b/plugins/stonesense index 37f6e626b..cb97cf308 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 37f6e626b054571b72535e2ac0ee3957e07432f1 +Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd From db98b06c6e17ade5cbce87b882177b189012a802 Mon Sep 17 00:00:00 2001 From: cherrydev Date: Fri, 15 Feb 2013 19:16:26 -0800 Subject: [PATCH 043/138] Added documentation of autolabor exemption from burrowed dwarves --- Readme.rst | 2 ++ plugins/autolabor.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Readme.rst b/Readme.rst index 236eafb01..672feb21a 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1693,6 +1693,8 @@ also tries to have dwarves specialize in specific skills. Warning: autolabor will override any manual changes you make to labors while it is enabled. + + To prevent particular dwarves from being managed by autolabor, put them in any burrow. For detailed usage information, see 'help autolabor'. diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index e5047b434..b1293055f 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -679,6 +679,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Fri, 15 Feb 2013 19:29:54 -0800 Subject: [PATCH 044/138] Regenerated HTML docs --- Readme.html | 194 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 127 insertions(+), 67 deletions(-) diff --git a/Readme.html b/Readme.html index 54deb013f..c6e784c3c 100644 --- a/Readme.html +++ b/Readme.html @@ -3,13 +3,13 @@ - + DFHack Readme