From b0892214007a09cde86f1093bbc7b33c1e2d5807 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 25 Oct 2012 00:09:34 +1300 Subject: [PATCH 01/73] 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 02/73] 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 03/73] 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 04/73] 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 05/73] 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 06/73] 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 07/73] 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 08/73] 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 09/73] 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 10/73] 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 11/73] 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 12/73] 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 13/73] 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 14/73] 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 15/73] 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 16/73] 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: Mon, 24 Dec 2012 00:59:12 +1300 Subject: [PATCH 17/73] 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 18/73] 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 19/73] 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 20/73] 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 21/73] 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 22/73] 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 23/73] 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 24/73] 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 25/73] 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 26/73] 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 27/73] 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 28/73] 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 29/73] 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 30/73] 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 31/73] 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 32/73] 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 33/73] 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 34/73] 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 35/73] 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 36/73] 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 37/73] 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 38/73] 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 39/73] 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 40/73] 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 4afe74efa6af69a9c6979722a9696ae1d204ba60 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 16 Feb 2013 12:10:47 +1300 Subject: [PATCH 41/73] Add DwarfMonitor plugin to monitor activities and happiness levels in a fort. --- plugins/CMakeLists.txt | 1 + plugins/buildingplan.cpp | 487 +------------------ plugins/dwarfmonitor.cpp | 987 +++++++++++++++++++++++++++++++++++++++ plugins/uicommon.h | 531 +++++++++++++++++++++ 4 files changed, 1528 insertions(+), 478 deletions(-) create mode 100644 plugins/dwarfmonitor.cpp create mode 100644 plugins/uicommon.h diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e1410116b..cdc0ef53d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -131,6 +131,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(buildingplan buildingplan.cpp) DFHACK_PLUGIN(resume resume.cpp) + DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp) endif() diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index c3a1f3867..ad00490b4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1,14 +1,6 @@ -#include -#include -#include -#include - -#include "Core.h" -#include -#include -#include -#include +#include "uicommon.h" +#include // DF data structure definition headers #include "DataDefs.h" @@ -24,11 +16,9 @@ #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" @@ -41,21 +31,12 @@ #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; -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.5 @@ -82,41 +63,6 @@ struct MaterialDescriptor } }; -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; @@ -127,429 +73,14 @@ struct coord32_t } }; -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; - } -} - -template -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) +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - transform(src.begin(), src.end(), back_inserter(dst), func); + return CR_OK; } - #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 multiselect; - bool allow_null; - bool auto_select; - bool force_sort; - bool allow_search; - - ListColumn() - { - clear(); - left_margin = 2; - bottom_margin = 3; - search_margin = 63; - highlighted_index = 0; - multiselect = false; - allow_null = true; - auto_select = false; - force_sort = false; - allow_search = true; - } - - 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 && allow_search) - { - y = gps->dimy - bottom_margin; - int32_t x = search_margin; - OutputHotkeyString(x, y, "Search" ,"S"); - OutputString(COLOR_WHITE, x, y, ": "); - OutputString(COLOR_WHITE, x, y, search_string); - OutputString(COLOR_LIGHTGREEN, x, y, "_"); - } - } - - void filterDisplay() - { - 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 (auto it = list.begin(); it != list.end(); it++) - { - if ((*it).selected) - { - results.push_back(&(it->elem)); - if (only_one) - break; - } - } - - return results; - } - - void clear_selection() - { - for_each_(list, [] (ListEntry &e) { e.selected = false; }); - } - - 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)) - { - changeHighlight(-1); - } - else if (input->count(interface_key::CURSOR_DOWN)) - { - changeHighlight(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) && !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) - { - return setHighlightByMouse(); - } - else if (allow_search) - { - // Search query typing mode always on - - df::interface_key last_token = *input->rbegin(); - if (last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) - { - // Standard character - search_string += last_token - ascii_to_enum_offset; - filterDisplay(); - } - else if (last_token == interface_key::STRING_A000) - { - // Backspace - if (search_string.length() > 0) - { - search_string.erase(search_string.length()-1); - filterDisplay(); - } - } - else - { - return false; - } - - return true; - } - 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; - } - - void sort() - { - if (force_sort || list.size() < 100) - std::sort(list.begin(), list.end(), - [] (ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; }); - - filterDisplay(); - } - - - private: - vector> list; - vector*> display_list; - string search_string; - int display_max_rows; - int max_item_width; -}; /* @@ -688,12 +219,12 @@ public: ViewscreenChooseMaterial(ItemFilter *filter) { selected_column = 0; - masks_column.title = "Type"; + masks_column.setTitle("Type"); masks_column.multiselect = true; masks_column.allow_search = false; masks_column.left_margin = 2; materials_column.left_margin = MAX_MASK + 3; - materials_column.title = "Material"; + materials_column.setTitle("Material"); materials_column.multiselect = true; this->filter = filter; @@ -709,7 +240,7 @@ public: void feed(set *input) { - bool key_processed; + bool key_processed = false; switch (selected_column) { case 0: @@ -734,8 +265,8 @@ public: if (input->count(interface_key::CUSTOM_SHIFT_C)) { filter->clear(); - masks_column.clear_selection(); - materials_column.clear_selection(); + masks_column.clearSelection(); + materials_column.clearSelection(); populateMaterials(); } else if (input->count(interface_key::SEC_SELECT)) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp new file mode 100644 index 000000000..95b576828 --- /dev/null +++ b/plugins/dwarfmonitor.cpp @@ -0,0 +1,987 @@ +#include "uicommon.h" + +#include "DataDefs.h" +#include "MiscUtils.h" + +#include "df/job.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" +#include "df/misc_trait_type.h" +#include "df/unit_misc_trait.h" + +#include "modules/Gui.h" +#include "modules/Units.h" +#include "modules/Translation.h" +#include "modules/World.h" +#include "modules/Maps.h" + +using std::deque; + +using df::global::gps; +using df::global::world; +using df::global::ui; + +typedef int16_t activity_type; + +#define PLUGIN_VERSION 0.2 +#define DAY_TICKS 1200 +#define DELTA_TICKS 100 + +int max_history_days = 7; + +template +struct less_second { + typedef pair type; + bool operator ()(type const& a, type const& b) const { + return a.second > b.second; + } +}; + +static bool monitor_jobs = false; +static bool monitor_misery = false; +static map> work_history; + +static int misery[] = { 0, 0, 0, 0, 0, 0, 0 }; +static bool misery_upto_date = false; + +static int get_max_history() +{ + return (DAY_TICKS / DELTA_TICKS) * max_history_days; +} + +static int getPercentage(const int n, const int d) +{ + return static_cast( + static_cast(n) / static_cast(d) * 100.0); +} + +static string getUnitName(df::unit * unit) +{ + string label = ""; + auto name = Units::getVisibleName(unit); + if (name->has_name) + label = Translation::TranslateName(name, false); + + return label; +} + +static void send_key(const df::interface_key &key) +{ + set< df::interface_key > keys; + keys.insert(key); + Gui::getCurViewscreen(true)->feed(&keys); +} + +static 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); +} + +static void open_stats_srceen(); + +#define JOB_UNKNOWN -2 +#define JOB_IDLE -1 +#define JOB_MILITARY -3 +#define JOB_LEISURE -4 +#define JOB_OTHER -5 +#define JOB_DESIG_SLOPE -6 +#define JOB_STORE_ITEM -7 +#define JOB_CONSTRUCT_FURNITURE -8 +#define JOB_DETAILING -9 +#define JOB_HUNTING -10 +#define JOB_CLOTING -11 +#define JOB_ARMOUR -12 +#define JOB_WEAPONS -13 +#define JOB_MEDICAL -14 + +static string getActivityLabel(const activity_type & activity) +{ + string label; + switch (activity) + { + case JOB_IDLE: + label = "Idle"; + break; + case JOB_MILITARY: + label = "Military Duty"; + break; + case JOB_LEISURE: + label = "Leisure"; + break; + case JOB_OTHER: + label = "Other"; + break; + case JOB_DESIG_SLOPE: + label = "Designate Stair/Slope"; + break; + case JOB_STORE_ITEM: + label = "Store Item"; + break; + case JOB_CONSTRUCT_FURNITURE: + label = "Construct Furniture"; + break; + case JOB_DETAILING: + label = "Detailing"; + break; + case JOB_HUNTING: + label = "Hunting"; + break; + case JOB_CLOTING: + label = "Make Clothing"; + break; + case JOB_ARMOUR: + label = "Make Armor"; + break; + case JOB_WEAPONS: + label = "Make Weapons"; + break; + case JOB_MEDICAL: + label = "Medical"; + break; + + default: + string raw_label = enum_item_key_str(static_cast(activity)); + for (auto c = raw_label.begin(); c != raw_label.end(); c++) + { + if (label.length() > 0 && *c >= 'A' && *c <= 'Z') + label += ' '; + + label += *c; + } + + break; + } + + return label; +} + +class ViewscreenDwarfStats : public dfhack_viewscreen +{ +public: + ViewscreenDwarfStats(df::unit *starting_selection) : selected_column(0) + { + dwarves_column.multiselect = false; + dwarves_column.auto_select = true; + dwarves_column.setTitle("Dwarves"); + + dwarf_activity_column.multiselect = false; + dwarf_activity_column.auto_select = true; + dwarf_activity_column.setTitle("Dwarf Activity"); + + for (auto it = work_history.begin(); it != work_history.end();) + { + auto unit = it->first; + if (Units::isDead(unit)) + { + work_history.erase(it++); + continue; + } + + deque *work_list = &it->second; + ++it; + + size_t dwarf_total = 0; + dwarf_activity_values[unit] = map(); + for (auto entry = work_list->begin(); entry != work_list->end(); entry++) + { + if (*entry == JOB_UNKNOWN || *entry == job_type::DrinkBlood) + continue; + + ++dwarf_total; + addDwarfActivity(unit, *entry); + } + + for_each_(dwarf_activity_values[unit], + [&] (const pair &x) + { dwarf_activity_values[unit][x.first] = getPercentage(x.second, dwarf_total); } ); + + dwarves_column.add(getUnitName(unit), *unit); + } + + dwarf_activity_column.left_margin = dwarves_column.fixWidth() + 2; + dwarves_column.filterDisplay(); + dwarves_column.selectItem(starting_selection); + populateActivityColumn(); + } + + void addDwarfActivity(df::unit *unit, const activity_type &activity) + { + if (dwarf_activity_values[unit].find(activity) == dwarf_activity_values[unit].end()) + dwarf_activity_values[unit][activity] = 0; + + dwarf_activity_values[unit][activity]++; + } + + void populateActivityColumn() + { + dwarf_activity_column.clear(); + if (dwarves_column.getDisplayedListSize() == 0) + return; + + auto unit = dwarves_column.getFirstSelectedElem(); + if (dwarf_activity_values.find(unit) == dwarf_activity_values.end()) + return; + + auto dwarf_activities = &dwarf_activity_values[unit]; + if (dwarf_activities) + { + vector> rev_vec(dwarf_activities->begin(), dwarf_activities->end()); + sort(rev_vec.begin(), rev_vec.end(), less_second()); + + for_each_(rev_vec, + [&] (pair x) + { dwarf_activity_column.add(getActivityItem(x.first, x.second), x.first); }); + } + + dwarf_activity_column.fixWidth(); + dwarf_activity_column.clearSearch(); + dwarf_activity_column.setHighlight(0); + } + + string getActivityItem(activity_type activity, size_t value) + { + return pad_string(int_to_string(value), 3) + " " + getActivityLabel(activity); + } + + void feed(set *input) + { + bool key_processed = false; + switch (selected_column) + { + case 0: + key_processed = dwarves_column.feed(input); + break; + case 1: + key_processed = dwarf_activity_column.feed(input); + break; + } + + if (key_processed) + { + if (selected_column == 0 && dwarves_column.feed_changed_highlight) + populateActivityColumn(); + + return; + } + + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + else if (input->count(interface_key::CUSTOM_SHIFT_D)) + { + Screen::dismiss(this); + open_stats_srceen(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_Z)) + { + df::unit *selected_unit = (selected_column == 0) ? dwarves_column.getFirstSelectedElem() : nullptr; + if (selected_unit) + { + input->clear(); + Screen::dismiss(this); + Gui::resetDwarfmodeView(true); + send_key(interface_key::D_VIEWUNIT); + move_cursor(selected_unit->pos); + } + } + 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 (dwarves_column.setHighlightByMouse()) + { + selected_column = 0; + populateActivityColumn(); + } + else if (dwarf_activity_column.setHighlightByMouse()) + selected_column = 1; + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + } + } + + void render() + { + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Dwarf Activity "); + + dwarves_column.display(selected_column == 0); + dwarf_activity_column.display(selected_column == 1); + + int32_t y = gps->dimy - 3; + int32_t x = 2; + OutputHotkeyString(x, y, "Leave", "Esc"); + x += 3; + OutputHotkeyString(x, y, "Fort Stats", "Shift-D"); + x += 3; + OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + } + + std::string getFocusString() { return "dwarfmonitor_dwarfstats"; } + +private: + ListColumn dwarves_column; + ListColumn dwarf_activity_column; + int selected_column; + + map> dwarf_activity_values; + + void validateColumn() + { + set_to_limit(selected_column, 1); + } + + void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + dwarves_column.resize(); + dwarf_activity_column.resize(); + } +}; + + +class ViewscreenFortStats : public dfhack_viewscreen +{ +public: + ViewscreenFortStats() : selected_column(0), fort_activity_count(0) + { + fort_activity_column.multiselect = false; + fort_activity_column.auto_select = true; + fort_activity_column.setTitle("Fort Activities"); + + dwarf_activity_column.multiselect = false; + dwarf_activity_column.auto_select = true; + dwarf_activity_column.setTitle("Units on Activity"); + + for (auto it = work_history.begin(); it != work_history.end();) + { + auto unit = it->first; + if (Units::isDead(unit)) + { + work_history.erase(it++); + continue; + } + + deque *work_list = &it->second; + ++it; + + for (auto entry = work_list->begin(); entry != work_list->end(); entry++) + { + if (*entry == JOB_UNKNOWN) + continue; + + ++fort_activity_count; + + auto real_activity = *entry; + if (real_activity < 0) + { + addFortActivity(real_activity); + } + else + { + auto activity = static_cast(real_activity); + + switch (activity) + { + case job_type::Eat: + case job_type::Drink: + case job_type::Drink2: + case job_type::Sleep: + case job_type::AttendParty: + case job_type::Rest: + case job_type::CleanSelf: + case job_type::DrinkBlood: + real_activity = JOB_LEISURE; + break; + + case job_type::Kidnap: + case job_type::StartingFistFight: + case job_type::SeekInfant: + case job_type::SeekArtifact: + case job_type::GoShopping: + case job_type::GoShopping2: + case job_type::RecoverPet: + case job_type::CauseTrouble: + case job_type::ReportCrime: + case job_type::BeatCriminal: + real_activity = JOB_OTHER; + break; + + case job_type::CarveUpwardStaircase: + case job_type::CarveDownwardStaircase: + case job_type::CarveUpDownStaircase: + case job_type::CarveRamp: + case job_type::DigChannel: + real_activity = JOB_DESIG_SLOPE; + break; + + case job_type::StoreOwnedItem: + case job_type::PlaceItemInTomb: + case job_type::StoreItemInStockpile: + case job_type::StoreItemInBag: + case job_type::StoreItemInHospital: + case job_type::StoreItemInChest: + case job_type::StoreItemInCabinet: + case job_type::StoreWeapon: + case job_type::StoreArmor: + case job_type::StoreItemInBarrel: + case job_type::StoreItemInBin: + real_activity = JOB_STORE_ITEM; + break; + + case job_type::ConstructDoor: + case job_type::ConstructFloodgate: + case job_type::ConstructBed: + case job_type::ConstructThrone: + case job_type::ConstructCoffin: + case job_type::ConstructTable: + case job_type::ConstructChest: + case job_type::ConstructBin: + case job_type::ConstructArmorStand: + case job_type::ConstructWeaponRack: + case job_type::ConstructCabinet: + case job_type::ConstructStatue: + real_activity = JOB_CONSTRUCT_FURNITURE; + break; + + case job_type::DetailFloor: + case job_type::DetailWall: + real_activity = JOB_DETAILING; + break; + + case job_type::Hunt: + case job_type::ReturnKill: + case job_type::HuntVermin: + real_activity = JOB_HUNTING; + break; + + default: + break; + } + + addFortActivity(real_activity); + } + + if (dwarf_activity_values.find(real_activity) == dwarf_activity_values.end()) + dwarf_activity_values[real_activity] = map(); + + map &activity_for_dwarf = dwarf_activity_values[real_activity]; + if (activity_for_dwarf.find(unit) == activity_for_dwarf.end()) + activity_for_dwarf[unit] = 0; + + ++activity_for_dwarf[unit]; + } + } + + vector> rev_vec(fort_activity_totals.begin(), fort_activity_totals.end()); + sort(rev_vec.begin(), rev_vec.end(), less_second()); + + for (auto rev_it = rev_vec.begin(); rev_it != rev_vec.end(); rev_it++) + { + auto activity = rev_it->first; + addToFortAverageColumn(activity); + + for (auto it = dwarf_activity_values[activity].begin(); it != dwarf_activity_values[activity].end(); it++) + { + auto avg = getPercentage(it->second, getFortActivityCount(activity)); + dwarf_activity_values[activity][it->first] = avg; + } + } + + dwarf_activity_column.left_margin = fort_activity_column.fixWidth() + 2; + fort_activity_column.filterDisplay(); + populateDwarfColumn(); + } + + void addToFortAverageColumn(activity_type type) + { + if (getFortActivityCount(type)) + fort_activity_column.add(getFortAverage(type), dwarf_activity_values[type]); + } + + string getFortAverage(const activity_type &activity) + { + auto average = getPercentage(getFortActivityCount(activity), fort_activity_count); + auto label = getActivityLabel(activity); + auto result = pad_string(int_to_string(average), 3) + " " + label; + + return result; + } + + string getDwarfAverage(df::unit *unit, const size_t value) + { + auto label = getUnitName(unit); + auto result = pad_string(int_to_string(value), 3) + " " + label; + + return result; + } + + size_t getFortActivityCount(const activity_type &activity) + { + if (fort_activity_totals.find(activity) == fort_activity_totals.end()) + return 0; + + return fort_activity_totals[activity]; + } + + void addFortActivity(const activity_type &activity) + { + if (fort_activity_totals.find(activity) == fort_activity_totals.end()) + fort_activity_totals[activity] = 0; + + fort_activity_totals[activity]++; + } + + void populateDwarfColumn() + { + dwarf_activity_column.clear(); + if (fort_activity_column.getDisplayListSize() == 0) + return; + + auto dwarf_activities = fort_activity_column.getFirstSelectedElem(); + if (dwarf_activities) + { + vector> rev_vec(dwarf_activities->begin(), dwarf_activities->end()); + sort(rev_vec.begin(), rev_vec.end(), less_second()); + + for_each_(rev_vec, + [&] (pair x) + { dwarf_activity_column.add(getDwarfAverage(x.first, x.second), *x.first); }); + } + + dwarf_activity_column.fixWidth(); + dwarf_activity_column.clearSearch(); + dwarf_activity_column.setHighlight(0); + } + + void feed(set *input) + { + bool key_processed = false; + switch (selected_column) + { + case 0: + key_processed = fort_activity_column.feed(input); + break; + case 1: + key_processed = dwarf_activity_column.feed(input); + break; + } + + if (key_processed) + { + if (selected_column == 0 && fort_activity_column.feed_changed_highlight) + populateDwarfColumn(); + + return; + } + + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + else if (input->count(interface_key::CUSTOM_SHIFT_D)) + { + df::unit *selected_unit = (selected_column == 1) ? dwarf_activity_column.getFirstSelectedElem() : nullptr; + Screen::dismiss(this); + Screen::show(new ViewscreenDwarfStats(selected_unit)); + } + else if (input->count(interface_key::CUSTOM_SHIFT_Z)) + { + df::unit *selected_unit = (selected_column == 1) ? dwarf_activity_column.getFirstSelectedElem() : nullptr; + if (selected_unit) + { + input->clear(); + Screen::dismiss(this); + Gui::resetDwarfmodeView(true); + send_key(interface_key::D_VIEWUNIT); + move_cursor(selected_unit->pos); + } + } + 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 (fort_activity_column.setHighlightByMouse()) + { + selected_column = 0; + populateDwarfColumn(); + } + else if (dwarf_activity_column.setHighlightByMouse()) + selected_column = 1; + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + } + } + + void render() + { + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Fortress Efficiency "); + + fort_activity_column.display(selected_column == 0); + dwarf_activity_column.display(selected_column == 1); + + int32_t y = gps->dimy - 3; + int32_t x = 2; + OutputHotkeyString(x, y, "Leave", "Esc"); + x += 3; + OutputHotkeyString(x, y, "Dwarf Stats", "Shift-D"); + x += 3; + OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); + } + + std::string getFocusString() { return "dwarfmonitor_fortstats"; } + +private: + ListColumn> fort_activity_column; + ListColumn dwarf_activity_column; + int selected_column; + + map fort_activity_totals; + map> dwarf_activity_values; + size_t fort_activity_count; + + void validateColumn() + { + set_to_limit(selected_column, 1); + } + + void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + fort_activity_column.resize(); + dwarf_activity_column.resize(); + } +}; + +static void open_stats_srceen() +{ + Screen::show(new ViewscreenFortStats()); +} + +static void add_work_history(df::unit *unit, activity_type type) +{ + if (work_history.find(unit) == work_history.end()) + { + auto max_history = get_max_history(); + for (int i = 0; i < max_history; i++) + work_history[unit].push_back(JOB_UNKNOWN); + } + + work_history[unit].push_back(type); + work_history[unit].pop_front(); +} + +static bool is_at_leisure(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) + return true; + } + + return false; +} + +static void reset() +{ + work_history.clear(); + + for (int i = 0; i < 7; i++) + misery[i] = 0; + + misery_upto_date = false; +} + +static void update_dwarf_stats(bool is_paused) +{ + if (monitor_misery) + { + for (int i = 0; i < 7; i++) + misery[i] = 0; + } + + for (auto iter = world->units.active.begin(); iter != world->units.active.end(); iter++) + { + df::unit* unit = *iter; + if (!Units::isCitizen(unit)) + continue; + + if (unit->profession == profession::BABY || + unit->profession == profession::CHILD || + unit->profession == profession::DRUNK) + { + continue; + } + + using namespace DFHack::Units; + if (!isSane(unit) || isDead(unit)) + { + auto it = work_history.find(unit); + if (it != work_history.end()) + work_history.erase(it); + + continue; + } + + if (monitor_misery) + { + int happy = unit->status.happiness; + if (happy == 0) // miserable + misery[0]++; + else if (happy <= 25) // very unhappy + misery[1]++; + else if (happy <= 50) // unhappy + misery[2]++; + else if (happy <= 75) // fine + misery[3]++; + else if (happy <= 125) // quite content + misery[4]++; + else if (happy <= 150) // happy + misery[5]++; + else // ecstatic + misery[6]++; + } + + if (!monitor_jobs || is_paused) + continue; + + if (ENUM_ATTR(profession, military, unit->profession)) + add_work_history(unit, JOB_MILITARY); + + if (!unit->job.current_job) + { + add_work_history(unit, JOB_IDLE); + continue; + } + + if (is_at_leisure(unit)) + { + add_work_history(unit, JOB_LEISURE); + continue; + } + + add_work_history(unit, unit->job.current_job->job_type); + } +} + +DFhackCExport command_result plugin_onupdate (color_ostream &out) +{ + if (!monitor_jobs && !monitor_misery) + return CR_OK; + + if(!Maps::IsValid()) + return CR_OK; + + static decltype(world->frame_counter) last_frame_count = 0; + + bool is_paused = Core::getInstance().getWorld()->ReadPauseState(); + if (is_paused) + { + if (monitor_misery && !misery_upto_date) + misery_upto_date = true; + else + return CR_OK; + } + else + { + if (world->frame_counter - last_frame_count < DELTA_TICKS) + return CR_OK; + + last_frame_count = world->frame_counter; + } + + update_dwarf_stats(is_paused); + + return CR_OK; +} + +struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (monitor_misery && Maps::IsValid()) + { + int x = 1; + int y = gps->dimy - 1; + OutputString(COLOR_WHITE, x, y, "H:"); + OutputString(COLOR_LIGHTRED, x, y, int_to_string(misery[0])); + OutputString(COLOR_WHITE, x, y, "/"); + OutputString(COLOR_RED, x, y, int_to_string(misery[1])); + OutputString(COLOR_WHITE, x, y, "/"); + OutputString(COLOR_YELLOW, x, y, int_to_string(misery[2])); + OutputString(COLOR_WHITE, x, y, "/"); + OutputString(COLOR_WHITE, x, y, int_to_string(misery[3])); + OutputString(COLOR_WHITE, x, y, "/"); + OutputString(COLOR_CYAN, x, y, int_to_string(misery[4])); + OutputString(COLOR_WHITE, x, y, "/"); + OutputString(COLOR_LIGHTBLUE, x, y, int_to_string(misery[5])); + OutputString(COLOR_WHITE, x, y, "/"); + OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(misery[6])); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render); + +DFHACK_PLUGIN("dwarfmonitor"); + +static bool set_monitoring_mode(const string &mode, const bool &state) +{ + bool mode_recognized = false; + + if (mode == "work" || mode == "all") + { + mode_recognized = true; + monitor_jobs = state; + if (!monitor_jobs) + reset(); + } + + if (mode == "misery" || mode == "all") + { + mode_recognized = true; + monitor_misery = state; + } + + return mode_recognized; +} + +static command_result dwarfmonitor_cmd(color_ostream &out, vector & parameters) +{ + bool show_help = false; + if (parameters.empty()) + { + show_help = true; + } + else + { + auto cmd = parameters[0][0]; + string mode; + + if (parameters.size() > 1) + mode = toLower(parameters[1]); + + if (cmd == 'v' || cmd == 'V') + { + out << "DwarfMonitor" << endl << "Version: " << PLUGIN_VERSION << endl; + } + else if ((cmd == 'e' || cmd == 'E') && !mode.empty()) + { + if (set_monitoring_mode(mode, true)) + { + out << "Monitoring enabled: " << mode << endl; + if (parameters.size() > 2) + { + int new_window = atoi(parameters[2].c_str()); + if (new_window > 0) + max_history_days = new_window; + } + } + else + { + show_help = true; + } + } + else if ((cmd == 'd' || cmd == 'D') && !mode.empty()) + { + if (set_monitoring_mode(mode, false)) + out << "Monitoring disabled: " << mode << endl; + else + show_help = true; + } + else if (cmd == 's' || cmd == 'S') + { + if(Maps::IsValid()) + Screen::show(new ViewscreenFortStats()); + } + 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(dwarf_monitor_hook, feed).apply() || !INTERPOSE_HOOK(dwarf_monitor_hook, render).apply()) + out.printerr("Could not insert dwarfmonitor hooks!\n"); + + commands.push_back( + PluginCommand( + "dwarfmonitor", "Records dwarf activity to measure fort efficiency", + dwarfmonitor_cmd, false, + "dwarfmonitor enable \n" + " Start monitoring [days]\n" + " can be \"work\", \"misery\", or \"all\"\n" + " [days] number of days to track (default is 7)\n\n" + "dwarfmonitor disable \n" + " as above\n\n" + "dwarfmonitor stats\n" + " Show statistics summary\n\n" + )); + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + reset(); + break; + default: + break; + } + return CR_OK; +} diff --git a/plugins/uicommon.h b/plugins/uicommon.h new file mode 100644 index 000000000..d4d52df4a --- /dev/null +++ b/plugins/uicommon.h @@ -0,0 +1,531 @@ +#include +#include +#include +#include + +#include "Core.h" +#include +#include +#include +#include + +#include "modules/Screen.h" + +#include "df/enabler.h" + + +using std::string; +using std::vector; +using std::map; +using std::ostringstream; +using std::set; + +using namespace DFHack; +using namespace df::enums; + +using df::global::enabler; + + +#ifndef HAVE_NULLPTR +#define nullptr 0L +#endif + +#define COLOR_TITLE COLOR_BLUE +#define COLOR_UNSELECTED COLOR_GREY +#define COLOR_SELECTED COLOR_WHITE +#define COLOR_HIGHLIGHTED COLOR_GREEN + + +template +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) +{ + transform(src.begin(), src.end(), back_inserter(dst), func); +} + +typedef int8_t UIColor; + +void OutputString(UIColor color, int &x, int &y, const std::string &text, + bool newline = false, int left_margin = 0, const UIColor bg_color = 0) +{ + Screen::paintString(Screen::Pen(' ', color, bg_color), 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); +} + +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; + } +} + + +/* + * List classes + */ +template +class ListEntry +{ +public: + T *elem; + string text; + bool selected; + + ListEntry(string text, T &elem) : elem(&elem), text(text), selected(false) + { + } +}; + +template +class ListColumn +{ +public: + int highlighted_index; + int display_start_offset; + int32_t bottom_margin, search_margin, left_margin; + bool multiselect; + bool allow_null; + bool auto_select; + bool force_sort; + bool allow_search; + bool feed_changed_highlight; + + ListColumn() + { + bottom_margin = 3; + clear(); + left_margin = 2; + search_margin = 63; + highlighted_index = 0; + multiselect = false; + allow_null = true; + auto_select = false; + force_sort = false; + allow_search = true; + feed_changed_highlight = 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(); + } + + int fixWidth() + { + for (auto it = list.begin(); it != list.end(); it++) + { + it->text = pad_string(it->text, max_item_width, false); + } + + return left_margin + max_item_width; + } + + 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 && allow_search) + { + y = gps->dimy - bottom_margin; + int32_t x = search_margin; + OutputHotkeyString(x, y, "Search" ,"S"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString(COLOR_WHITE, x, y, search_string); + OutputString(COLOR_LIGHTGREEN, x, y, "_"); + } + } + + void filterDisplay() + { + ListEntry *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; + display_list.clear(); + search_string = toLower(search_string); + for (size_t i = 0; i < list.size(); i++) + { + if (search_string.empty() || toLower(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); + feed_changed_highlight = true; + } + + 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; + + feed_changed_highlight = 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 (auto 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 (auto it = list.begin(); it != list.end(); it++) + { + if ((*it).selected) + { + results.push_back(it->elem); + if (only_one) + break; + } + } + + return results; + } + + void clearSelection() + { + for_each_(list, [] (ListEntry &e) { e.selected = false; }); + } + + void selectItem(const T* elem) + { + if (!elem) + return; + + //clearSelection(); + int i = 0; + for (; i < display_list.size(); i++) + { + if (display_list[i]->elem == elem) + { + setHighlight(i); + break; + } + } + } + + void clearSearch() + { + search_string.clear(); + filterDisplay(); + } + + T* getFirstSelectedElem() + { + vector results = getSelectedElems(true); + if (results.size() == 0) + return nullptr; + else + return results[0]; + } + + size_t getDisplayListSize() + { + return display_list.size(); + } + + size_t getBaseListSize() + { + return list.size(); + } + + bool feed(set *input) + { + feed_changed_highlight = false; + 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::STANDARDSCROLL_PAGEUP)) + { + changeHighlight(0, -1); + } + else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN)) + { + changeHighlight(0, 1); + } + else if (input->count(interface_key::SELECT) && !auto_select) + { + toggleHighlighted(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_S)) + { + clearSearch(); + } + else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut) + { + return setHighlightByMouse(); + } + else if (allow_search) + { + // Search query typing mode always on + df::interface_key last_token = *input->rbegin(); + if (last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) + { + // Standard character + search_string += last_token - ascii_to_enum_offset; + filterDisplay(); + } + else if (last_token == interface_key::STRING_A000) + { + // Backspace + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + filterDisplay(); + } + } + else + { + return false; + } + + return true; + } + 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; + } + + void sort() + { + if (force_sort || list.size() < 100) + std::sort(list.begin(), list.end(), + [] (ListEntry const& a, ListEntry const& b) { return a.text.compare(b.text) < 0; }); + + filterDisplay(); + } + + void setTitle(const string t) + { + title = t; + if (title.length() > max_item_width) + max_item_width = title.length(); + } + + size_t getDisplayedListSize() + { + return display_list.size(); + } + +private: + vector> list; + vector*> display_list; + string search_string; + string title; + int display_max_rows; + int max_item_width; +}; + + From 701b7b7d9b1589824c1f077133f58207b4f1f5a8 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 1 Mar 2013 21:29:16 +1300 Subject: [PATCH 42/73] Multiple data averaging windows and gcc fixes. --- plugins/buildingplan.cpp | 2 - plugins/dwarfmonitor.cpp | 110 ++++++++++++++++++++++++--------------- plugins/uicommon.h | 4 +- 3 files changed, 72 insertions(+), 44 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ad00490b4..cb6bcdc73 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -4,7 +4,6 @@ // 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" @@ -33,7 +32,6 @@ #include "modules/World.h" #include "df/building.h" -using df::global::gps; using df::global::ui; using df::global::ui_build_selector; using df::global::world; diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 95b576828..6a4e3c2ea 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1,7 +1,6 @@ #include "uicommon.h" #include "DataDefs.h" -#include "MiscUtils.h" #include "df/job.h" #include "df/ui.h" @@ -19,17 +18,18 @@ using std::deque; -using df::global::gps; using df::global::world; using df::global::ui; typedef int16_t activity_type; -#define PLUGIN_VERSION 0.2 +#define PLUGIN_VERSION 0.4 #define DAY_TICKS 1200 #define DELTA_TICKS 100 -int max_history_days = 7; +const int min_window = 28; +const int max_history_days = 3 * min_window; +const int ticks_per_day = DAY_TICKS / DELTA_TICKS; template struct less_second { @@ -40,7 +40,7 @@ struct less_second { }; static bool monitor_jobs = false; -static bool monitor_misery = false; +static bool monitor_misery = true; static map> work_history; static int misery[] = { 0, 0, 0, 0, 0, 0, 0 }; @@ -48,7 +48,7 @@ static bool misery_upto_date = false; static int get_max_history() { - return (DAY_TICKS / DELTA_TICKS) * max_history_days; + return ticks_per_day * max_history_days; } static int getPercentage(const int n, const int d) @@ -363,15 +363,32 @@ private: class ViewscreenFortStats : public dfhack_viewscreen { public: - ViewscreenFortStats() : selected_column(0), fort_activity_count(0) + ViewscreenFortStats() { fort_activity_column.multiselect = false; fort_activity_column.auto_select = true; fort_activity_column.setTitle("Fort Activities"); + fort_activity_column.bottom_margin = 4; dwarf_activity_column.multiselect = false; dwarf_activity_column.auto_select = true; dwarf_activity_column.setTitle("Units on Activity"); + dwarf_activity_column.bottom_margin = 4; + + window_days = min_window; + + populateFortColumn(); + } + + void populateFortColumn() + { + selected_column = 0; + fort_activity_count = 0; + + auto last_selected_index = fort_activity_column.highlighted_index; + fort_activity_column.clear(); + fort_activity_totals.clear(); + dwarf_activity_values.clear(); for (auto it = work_history.begin(); it != work_history.end();) { @@ -385,7 +402,8 @@ public: deque *work_list = &it->second; ++it; - for (auto entry = work_list->begin(); entry != work_list->end(); entry++) + size_t count = window_days * ticks_per_day; + for (auto entry = work_list->rbegin(); entry != work_list->rend() && count > 0; entry++, count--) { if (*entry == JOB_UNKNOWN) continue; @@ -495,7 +513,7 @@ public: vector> rev_vec(fort_activity_totals.begin(), fort_activity_totals.end()); sort(rev_vec.begin(), rev_vec.end(), less_second()); - + for (auto rev_it = rev_vec.begin(); rev_it != rev_vec.end(); rev_it++) { auto activity = rev_it->first; @@ -510,9 +528,32 @@ public: dwarf_activity_column.left_margin = fort_activity_column.fixWidth() + 2; fort_activity_column.filterDisplay(); + fort_activity_column.setHighlight(last_selected_index); populateDwarfColumn(); } + void populateDwarfColumn() + { + dwarf_activity_column.clear(); + if (fort_activity_column.getDisplayListSize() == 0) + return; + + auto dwarf_activities = fort_activity_column.getFirstSelectedElem(); + if (dwarf_activities) + { + vector> rev_vec(dwarf_activities->begin(), dwarf_activities->end()); + sort(rev_vec.begin(), rev_vec.end(), less_second()); + + for_each_(rev_vec, + [&] (pair x) + { dwarf_activity_column.add(getDwarfAverage(x.first, x.second), *x.first); }); + } + + dwarf_activity_column.fixWidth(); + dwarf_activity_column.clearSearch(); + dwarf_activity_column.setHighlight(0); + } + void addToFortAverageColumn(activity_type type) { if (getFortActivityCount(type)) @@ -552,28 +593,6 @@ public: fort_activity_totals[activity]++; } - void populateDwarfColumn() - { - dwarf_activity_column.clear(); - if (fort_activity_column.getDisplayListSize() == 0) - return; - - auto dwarf_activities = fort_activity_column.getFirstSelectedElem(); - if (dwarf_activities) - { - vector> rev_vec(dwarf_activities->begin(), dwarf_activities->end()); - sort(rev_vec.begin(), rev_vec.end(), less_second()); - - for_each_(rev_vec, - [&] (pair x) - { dwarf_activity_column.add(getDwarfAverage(x.first, x.second), *x.first); }); - } - - dwarf_activity_column.fixWidth(); - dwarf_activity_column.clearSearch(); - dwarf_activity_column.setHighlight(0); - } - void feed(set *input) { bool key_processed = false; @@ -601,6 +620,14 @@ public: Screen::dismiss(this); return; } + else if (input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + window_days += min_window; + if (window_days > max_history_days) + window_days = min_window; + + populateFortColumn(); + } else if (input->count(interface_key::CUSTOM_SHIFT_D)) { df::unit *selected_unit = (selected_column == 1) ? dwarf_activity_column.getFirstSelectedElem() : nullptr; @@ -656,11 +683,18 @@ public: fort_activity_column.display(selected_column == 0); dwarf_activity_column.display(selected_column == 1); - int32_t y = gps->dimy - 3; + int32_t y = gps->dimy - 4; int32_t x = 2; OutputHotkeyString(x, y, "Leave", "Esc"); - x += 3; + + x += 13; + string window_label = "Window Months: " + int_to_string(window_days / min_window); + OutputHotkeyString(x, y, window_label.c_str(), "*"); + + ++y; + x = 2; OutputHotkeyString(x, y, "Dwarf Stats", "Shift-D"); + x += 3; OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); } @@ -675,6 +709,7 @@ private: map fort_activity_totals; map> dwarf_activity_values; size_t fort_activity_count; + size_t window_days; void validateColumn() { @@ -916,12 +951,6 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector & par if (set_monitoring_mode(mode, true)) { out << "Monitoring enabled: " << mode << endl; - if (parameters.size() > 2) - { - int new_window = atoi(parameters[2].c_str()); - if (new_window > 0) - max_history_days = new_window; - } } else { @@ -962,9 +991,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" - " Start monitoring [days]\n" + " Start monitoring \n" " can be \"work\", \"misery\", or \"all\"\n" - " [days] number of days to track (default is 7)\n\n" "dwarfmonitor disable \n" " as above\n\n" "dwarfmonitor stats\n" diff --git a/plugins/uicommon.h b/plugins/uicommon.h index d4d52df4a..a936aecfe 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -4,6 +4,7 @@ #include #include "Core.h" +#include "MiscUtils.h" #include #include #include @@ -24,6 +25,7 @@ using namespace DFHack; using namespace df::enums; using df::global::enabler; +using df::global::gps; #ifndef HAVE_NULLPTR @@ -184,7 +186,7 @@ public: list.clear(); display_list.clear(); display_start_offset = 0; - max_item_width = 0; + max_item_width = title.length(); resize(); } From 63707471f48673ec92cd2275330ff9282b639165 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 2 Mar 2013 21:58:36 +1300 Subject: [PATCH 43/73] WIP - More logical categories and variable windows --- plugins/buildingplan.cpp | 27 +-- plugins/dwarfmonitor.cpp | 453 +++++++++++++++++++++++++++++++-------- plugins/uicommon.h | 48 +++-- 3 files changed, 405 insertions(+), 123 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index cb6bcdc73..a1c3041e8 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -278,12 +278,12 @@ public: // Category masks auto masks = masks_column.getSelectedElems(); for_each_(masks, - [&] (df::dfhack_material_category *m) { filter->mat_mask.whole |= m->whole; }); + [&] (df::dfhack_material_category &m) { filter->mat_mask.whole |= m.whole; }); // Specific materials auto materials = materials_column.getSelectedElems(); transform_(materials, filter->materials, - [] (MaterialInfo *m) { return *m; }); + [] (MaterialInfo &m) { return m; }); Screen::dismiss(this); } @@ -349,7 +349,6 @@ private: entry.selected = true; masks_column.add(entry); - mask.whole = 0; } void populateMasks() @@ -357,12 +356,19 @@ private: masks_column.clear(); df::dfhack_material_category mask; + mask.whole = 0; mask.bits.stone = true; addMaskEntry(mask, "Stone"); + + mask.whole = 0; mask.bits.wood = true; addMaskEntry(mask, "Wood"); + + mask.whole = 0; mask.bits.metal = true; addMaskEntry(mask, "Metal"); + + mask.whole = 0; mask.bits.soap = true; addMaskEntry(mask, "Soap"); @@ -373,9 +379,9 @@ private: { materials_column.clear(); df::dfhack_material_category selected_category; - vector selected_masks = masks_column.getSelectedElems(); + vector selected_masks = masks_column.getSelectedElems(); if (selected_masks.size() == 1) - selected_category = *selected_masks[0]; + selected_category = selected_masks[0]; else if (selected_masks.size() > 1) return; @@ -406,14 +412,6 @@ private: 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);*/ - - - //addMaterialEntry(selected_category, material, material.toString()); - for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++) { auto t = p->material[j]; @@ -422,18 +420,15 @@ private: 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(); } diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 6a4e3c2ea..eb0d01c30 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -15,6 +15,8 @@ #include "modules/Translation.h" #include "modules/World.h" #include "modules/Maps.h" +#include "df/activity_event.h" +#include "df/activity_entry.h" using std::deque; @@ -83,67 +85,37 @@ static void move_cursor(df::coord &pos) static void open_stats_srceen(); -#define JOB_UNKNOWN -2 #define JOB_IDLE -1 +#define JOB_UNKNOWN -2 #define JOB_MILITARY -3 #define JOB_LEISURE -4 -#define JOB_OTHER -5 -#define JOB_DESIG_SLOPE -6 +#define JOB_UNPRODUCTIVE -5 +#define JOB_DESIGNATE -6 #define JOB_STORE_ITEM -7 -#define JOB_CONSTRUCT_FURNITURE -8 +#define JOB_MANUFACTURE -8 #define JOB_DETAILING -9 #define JOB_HUNTING -10 -#define JOB_CLOTING -11 -#define JOB_ARMOUR -12 -#define JOB_WEAPONS -13 #define JOB_MEDICAL -14 +#define JOB_COLLECT -15 +#define JOB_CONSTRUCTION -16 +#define JOB_AGRICULTURE -17 +#define JOB_FOOD_PROD -18 +#define JOB_MECHANICAL -19 +#define JOB_ANIMALS -20 +#define JOB_PRODUCTIVE -21 + +static map activity_labels; -static string getActivityLabel(const activity_type & activity) +static string getActivityLabel(const activity_type activity) { string label; - switch (activity) - { - case JOB_IDLE: - label = "Idle"; - break; - case JOB_MILITARY: - label = "Military Duty"; - break; - case JOB_LEISURE: - label = "Leisure"; - break; - case JOB_OTHER: - label = "Other"; - break; - case JOB_DESIG_SLOPE: - label = "Designate Stair/Slope"; - break; - case JOB_STORE_ITEM: - label = "Store Item"; - break; - case JOB_CONSTRUCT_FURNITURE: - label = "Construct Furniture"; - break; - case JOB_DETAILING: - label = "Detailing"; - break; - case JOB_HUNTING: - label = "Hunting"; - break; - case JOB_CLOTING: - label = "Make Clothing"; - break; - case JOB_ARMOUR: - label = "Make Armor"; - break; - case JOB_WEAPONS: - label = "Make Weapons"; - break; - case JOB_MEDICAL: - label = "Medical"; - break; - default: + if (activity_labels.find(activity) != activity_labels.end()) + { + label = activity_labels[activity]; + } + else + { string raw_label = enum_item_key_str(static_cast(activity)); for (auto c = raw_label.begin(); c != raw_label.end(); c++) { @@ -152,8 +124,6 @@ static string getActivityLabel(const activity_type & activity) label += *c; } - - break; } return label; @@ -172,7 +142,20 @@ public: dwarf_activity_column.auto_select = true; dwarf_activity_column.setTitle("Dwarf Activity"); - for (auto it = work_history.begin(); it != work_history.end();) + window_days = min_window; + + populateDwarfColumn(starting_selection); + } + + void populateDwarfColumn(df::unit *starting_selection = NULL) + { + selected_column = 0; + + auto last_selected_index = dwarf_activity_column.highlighted_index; + dwarf_activity_column.clear(); + dwarf_activity_values.clear(); + + for (auto it = work_history.begin(); it != work_history.end(); it++) { auto unit = it->first; if (Units::isDead(unit)) @@ -186,7 +169,8 @@ public: size_t dwarf_total = 0; dwarf_activity_values[unit] = map(); - for (auto entry = work_list->begin(); entry != work_list->end(); entry++) + size_t count = window_days * ticks_per_day; + for (auto entry = work_list->rbegin(); entry != work_list->rend() && count > 0; entry++, count--) { if (*entry == JOB_UNKNOWN || *entry == job_type::DrinkBlood) continue; @@ -197,23 +181,19 @@ public: for_each_(dwarf_activity_values[unit], [&] (const pair &x) - { dwarf_activity_values[unit][x.first] = getPercentage(x.second, dwarf_total); } ); + { dwarf_activity_values[unit][x.first] = getPercentage(x.second, dwarf_total); } ); - dwarves_column.add(getUnitName(unit), *unit); + dwarves_column.add(getUnitName(unit), unit); } dwarf_activity_column.left_margin = dwarves_column.fixWidth() + 2; dwarves_column.filterDisplay(); - dwarves_column.selectItem(starting_selection); - populateActivityColumn(); - } - - void addDwarfActivity(df::unit *unit, const activity_type &activity) - { - if (dwarf_activity_values[unit].find(activity) == dwarf_activity_values[unit].end()) - dwarf_activity_values[unit][activity] = 0; + if (starting_selection) + dwarves_column.selectItem(starting_selection); + else + dwarves_column.setHighlight(last_selected_index); - dwarf_activity_values[unit][activity]++; + populateActivityColumn(); } void populateActivityColumn() @@ -242,6 +222,14 @@ public: dwarf_activity_column.setHighlight(0); } + void addDwarfActivity(df::unit *unit, const activity_type &activity) + { + if (dwarf_activity_values[unit].find(activity) == dwarf_activity_values[unit].end()) + dwarf_activity_values[unit][activity] = 0; + + dwarf_activity_values[unit][activity]++; + } + string getActivityItem(activity_type activity, size_t value) { return pad_string(int_to_string(value), 3) + " " + getActivityLabel(activity); @@ -291,6 +279,14 @@ public: move_cursor(selected_unit->pos); } } + else if (input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + window_days += min_window; + if (window_days > max_history_days) + window_days = min_window; + + populateDwarfColumn(); + } else if (input->count(interface_key::CURSOR_LEFT)) { --selected_column; @@ -328,11 +324,18 @@ public: dwarves_column.display(selected_column == 0); dwarf_activity_column.display(selected_column == 1); - int32_t y = gps->dimy - 3; + int32_t y = gps->dimy - 4; int32_t x = 2; OutputHotkeyString(x, y, "Leave", "Esc"); - x += 3; + + x += 13; + string window_label = "Window Months: " + int_to_string(window_days / min_window); + OutputHotkeyString(x, y, window_label.c_str(), "*"); + + ++y; + x = 2; OutputHotkeyString(x, y, "Fort Stats", "Shift-D"); + x += 3; OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z"); } @@ -340,9 +343,10 @@ public: std::string getFocusString() { return "dwarfmonitor_dwarfstats"; } private: - ListColumn dwarves_column; + ListColumn dwarves_column; ListColumn dwarf_activity_column; int selected_column; + size_t window_days; map> dwarf_activity_values; @@ -374,6 +378,10 @@ public: dwarf_activity_column.auto_select = true; dwarf_activity_column.setTitle("Units on Activity"); dwarf_activity_column.bottom_margin = 4; + dwarf_activity_column.text_clip_at = 25; + + category_breakdown_column.setTitle("Category Breakdown"); + category_breakdown_column.bottom_margin = 4; window_days = min_window; @@ -389,6 +397,7 @@ public: fort_activity_column.clear(); fort_activity_totals.clear(); dwarf_activity_values.clear(); + category_breakdown.clear(); for (auto it = work_history.begin(); it != work_history.end();) { @@ -442,7 +451,8 @@ public: case job_type::CauseTrouble: case job_type::ReportCrime: case job_type::BeatCriminal: - real_activity = JOB_OTHER; + case job_type::ExecuteCriminal: + real_activity = JOB_UNPRODUCTIVE; break; case job_type::CarveUpwardStaircase: @@ -450,7 +460,10 @@ public: case job_type::CarveUpDownStaircase: case job_type::CarveRamp: case job_type::DigChannel: - real_activity = JOB_DESIG_SLOPE; + case job_type::Dig: + case job_type::CarveTrack: + case job_type::CarveFortification: + real_activity = JOB_DESIGNATE; break; case job_type::StoreOwnedItem: @@ -464,6 +477,17 @@ public: case job_type::StoreArmor: case job_type::StoreItemInBarrel: case job_type::StoreItemInBin: + case job_type::BringItemToDepot: + case job_type::BringItemToShop: + case job_type::GetProvisions: + case job_type::FillWaterskin: + case job_type::FillWaterskin2: + case job_type::CheckChest: + case job_type::PickupEquipment: + case job_type::DumpItem: + case job_type::PushTrackVehicle: + case job_type::PlaceTrackVehicle: + case job_type::StoreItemInVehicle: real_activity = JOB_STORE_ITEM; break; @@ -479,7 +503,76 @@ public: case job_type::ConstructWeaponRack: case job_type::ConstructCabinet: case job_type::ConstructStatue: - real_activity = JOB_CONSTRUCT_FURNITURE; + case job_type::ConstructBlocks: + case job_type::MakeRawGlass: + case job_type::MakeCrafts: + case job_type::MintCoins: + case job_type::CutGems: + case job_type::CutGlass: + case job_type::EncrustWithGems: + case job_type::EncrustWithGlass: + case job_type::SmeltOre: + case job_type::MeltMetalObject: + case job_type::ExtractMetalStrands: + case job_type::MakeWeapon: + case job_type::ForgeAnvil: + case job_type::ConstructCatapultParts: + case job_type::ConstructBallistaParts: + case job_type::MakeArmor: + case job_type::MakeHelm: + case job_type::MakePants: + case job_type::StudWith: + case job_type::ProcessPlantsBag: + case job_type::ProcessPlantsVial: + case job_type::ProcessPlantsBarrel: + case job_type::WeaveCloth: + case job_type::MakeGloves: + case job_type::MakeShoes: + case job_type::MakeShield: + case job_type::MakeCage: + case job_type::MakeChain: + case job_type::MakeFlask: + case job_type::MakeGoblet: + case job_type::MakeInstrument: + case job_type::MakeToy: + case job_type::MakeAnimalTrap: + case job_type::MakeBarrel: + case job_type::MakeBucket: + case job_type::MakeWindow: + case job_type::MakeTotem: + case job_type::MakeAmmo: + case job_type::DecorateWith: + case job_type::MakeBackpack: + case job_type::MakeQuiver: + case job_type::MakeBallistaArrowHead: + case job_type::AssembleSiegeAmmo: + case job_type::ConstructMechanisms: + case job_type::MakeTrapComponent: + case job_type::ExtractFromPlants: + case job_type::ExtractFromRawFish: + case job_type::ExtractFromLandAnimal: + case job_type::MakeCharcoal: + case job_type::MakeAsh: + case job_type::MakeLye: + case job_type::MakePotashFromLye: + case job_type::MakePotashFromAsh: + case job_type::DyeThread: + case job_type::DyeCloth: + case job_type::SewImage: + case job_type::MakePipeSection: + case job_type::ConstructHatchCover: + case job_type::ConstructGrate: + case job_type::ConstructQuern: + case job_type::ConstructMillstone: + case job_type::ConstructSplint: + case job_type::ConstructCrutch: + case job_type::ConstructTractionBench: + case job_type::CustomReaction: + case job_type::ConstructSlab: + case job_type::EngraveSlab: + case job_type::SpinThread: + case job_type::MakeTool: + real_activity = JOB_MANUFACTURE; break; case job_type::DetailFloor: @@ -490,14 +583,121 @@ public: case job_type::Hunt: case job_type::ReturnKill: case job_type::HuntVermin: + case job_type::GatherPlants: + case job_type::Fish: + case job_type::CatchLiveFish: + case job_type::BaitTrap: + case job_type::InstallColonyInHive: real_activity = JOB_HUNTING; break; + case job_type::RemoveConstruction: + case job_type::DestroyBuilding: + case job_type::RemoveStairs: + case job_type::ConstructBuilding: + real_activity = JOB_CONSTRUCTION; + break; + + case job_type::FellTree: + case job_type::CollectWebs: + case job_type::CollectSand: + case job_type::DrainAquarium: + case job_type::FillAquarium: + case job_type::FillPond: + case job_type::CollectClay: + real_activity = JOB_COLLECT; + break; + + case job_type::TrainHuntingAnimal: + case job_type::TrainWarAnimal: + case job_type::CatchLiveLandAnimal: + case job_type::TameVermin: + case job_type::TameAnimal: + case job_type::ChainAnimal: + case job_type::UnchainAnimal: + case job_type::UnchainPet: + case job_type::ReleaseLargeCreature: + case job_type::ReleasePet: + case job_type::ReleaseSmallCreature: + case job_type::HandleSmallCreature: + case job_type::HandleLargeCreature: + case job_type::CageLargeCreature: + case job_type::CageSmallCreature: + case job_type::PitLargeAnimal: + case job_type::PitSmallAnimal: + case job_type::SlaughterAnimal: + case job_type::ShearCreature: + case job_type::PenLargeAnimal: + case job_type::PenSmallAnimal: + case job_type::TrainAnimal: + real_activity = JOB_ANIMALS; + break; + + case job_type::PlantSeeds: + case job_type::HarvestPlants: + case job_type::FertilizeField: + real_activity = JOB_AGRICULTURE; + break; + + case job_type::ButcherAnimal: + case job_type::PrepareRawFish: + case job_type::MillPlants: + case job_type::MilkCreature: + case job_type::MakeCheese: + case job_type::PrepareMeal: + case job_type::ProcessPlants: + case job_type::BrewDrink: + case job_type::CollectHiveProducts: + real_activity = JOB_FOOD_PROD; + break; + + case job_type::LoadCatapult: + case job_type::LoadBallista: + case job_type::FireCatapult: + case job_type::FireBallista: + real_activity = JOB_MILITARY; + break; + + case job_type::LoadCageTrap: + case job_type::LoadStoneTrap: + case job_type::LoadWeaponTrap: + case job_type::CleanTrap: + case job_type::LinkBuildingToTrigger: + case job_type::PullLever: + real_activity = JOB_MECHANICAL; + break; + + case job_type::RecoverWounded: + case job_type::DiagnosePatient: + case job_type::ImmobilizeBreak: + case job_type::DressWound: + case job_type::CleanPatient: + case job_type::Surgery: + case job_type::Suture: + case job_type::SetBone: + case job_type::PlaceInTraction: + case job_type::GiveWater: + case job_type::GiveFood: + case job_type::GiveWater2: + case job_type::GiveFood2: + case job_type::BringCrutch: + case job_type::ApplyCast: + real_activity = JOB_MEDICAL; + break; + + case job_type::OperatePump: + case job_type::ManageWorkOrders: + case job_type::UpdateStockpileRecords: + case job_type::TradeAtDepot: + real_activity = JOB_PRODUCTIVE; + break; + default: break; } addFortActivity(real_activity); + addCategoryActivity(real_activity, *entry); } if (dwarf_activity_values.find(real_activity) == dwarf_activity_values.end()) @@ -526,38 +726,72 @@ public: } } + for (auto cat_it = category_breakdown.begin(); cat_it != category_breakdown.end(); cat_it++) + { + auto cat_total = fort_activity_totals[cat_it->first]; + for (auto val_it = cat_it->second.begin(); val_it != cat_it->second.end(); val_it++) + { + category_breakdown[cat_it->first][val_it->first] = getPercentage(val_it->second, cat_total); + } + } + dwarf_activity_column.left_margin = fort_activity_column.fixWidth() + 2; fort_activity_column.filterDisplay(); fort_activity_column.setHighlight(last_selected_index); populateDwarfColumn(); + populateCategoryBreakdownColumn(); } void populateDwarfColumn() { dwarf_activity_column.clear(); + if (fort_activity_column.getDisplayListSize() > 0) + { + activity_type selected_activity = fort_activity_column.getFirstSelectedElem(); + auto dwarf_activities = &dwarf_activity_values[selected_activity]; + if (dwarf_activities) + { + vector> rev_vec(dwarf_activities->begin(), dwarf_activities->end()); + sort(rev_vec.begin(), rev_vec.end(), less_second()); + + for_each_(rev_vec, + [&] (pair x) + { dwarf_activity_column.add(getDwarfAverage(x.first, x.second), x.first); }); + } + } + + category_breakdown_column.left_margin = dwarf_activity_column.fixWidth() + 2; + dwarf_activity_column.clearSearch(); + dwarf_activity_column.setHighlight(0); + } + + void populateCategoryBreakdownColumn() + { + category_breakdown_column.clear(); if (fort_activity_column.getDisplayListSize() == 0) return; - auto dwarf_activities = fort_activity_column.getFirstSelectedElem(); - if (dwarf_activities) + auto selected_activity = fort_activity_column.getFirstSelectedElem(); + auto category_activities = &category_breakdown[selected_activity]; + if (category_activities) { - vector> rev_vec(dwarf_activities->begin(), dwarf_activities->end()); - sort(rev_vec.begin(), rev_vec.end(), less_second()); + vector> rev_vec(category_activities->begin(), category_activities->end()); + sort(rev_vec.begin(), rev_vec.end(), less_second()); for_each_(rev_vec, - [&] (pair x) - { dwarf_activity_column.add(getDwarfAverage(x.first, x.second), *x.first); }); + [&] (pair x) + { category_breakdown_column.add(getBreakdownAverage(x.first, x.second), x.first); }); } - dwarf_activity_column.fixWidth(); - dwarf_activity_column.clearSearch(); - dwarf_activity_column.setHighlight(0); + category_breakdown_column.fixWidth(); + category_breakdown_column.clearSearch(); + category_breakdown_column.setHighlight(0); } - void addToFortAverageColumn(activity_type type) + void addToFortAverageColumn(activity_type &type) { if (getFortActivityCount(type)) - fort_activity_column.add(getFortAverage(type), dwarf_activity_values[type]); + fort_activity_column.add(getFortAverage(type), type); } string getFortAverage(const activity_type &activity) @@ -577,7 +811,15 @@ public: return result; } - size_t getFortActivityCount(const activity_type &activity) + string getBreakdownAverage(activity_type activity, const size_t value) + { + auto label = getActivityLabel(activity); + auto result = pad_string(int_to_string(value), 3) + " " + label; + + return result; + } + + size_t getFortActivityCount(const activity_type activity) { if (fort_activity_totals.find(activity) == fort_activity_totals.end()) return 0; @@ -585,7 +827,7 @@ public: return fort_activity_totals[activity]; } - void addFortActivity(const activity_type &activity) + void addFortActivity(const activity_type activity) { if (fort_activity_totals.find(activity) == fort_activity_totals.end()) fort_activity_totals[activity] = 0; @@ -593,6 +835,17 @@ public: fort_activity_totals[activity]++; } + void addCategoryActivity(const int category, const activity_type activity) + { + if (category_breakdown.find(category) == category_breakdown.end()) + category_breakdown[category] = map(); + + if (category_breakdown[category].find(activity) == category_breakdown[category].end()) + category_breakdown[category][activity] = 0; + + category_breakdown[category][activity]++; + } + void feed(set *input) { bool key_processed = false; @@ -609,7 +862,10 @@ public: if (key_processed) { if (selected_column == 0 && fort_activity_column.feed_changed_highlight) + { populateDwarfColumn(); + populateCategoryBreakdownColumn(); + } return; } @@ -682,6 +938,7 @@ public: fort_activity_column.display(selected_column == 0); dwarf_activity_column.display(selected_column == 1); + category_breakdown_column.display(false); int32_t y = gps->dimy - 4; int32_t x = 2; @@ -702,14 +959,17 @@ public: std::string getFocusString() { return "dwarfmonitor_fortstats"; } private: - ListColumn> fort_activity_column; - ListColumn dwarf_activity_column; + ListColumn fort_activity_column, category_breakdown_column; + ListColumn dwarf_activity_column; int selected_column; map fort_activity_totals; + map> category_breakdown; map> dwarf_activity_values; size_t fort_activity_count; size_t window_days; + + vector listed_activities; void validateColumn() { @@ -817,7 +1077,10 @@ static void update_dwarf_stats(bool is_paused) continue; if (ENUM_ATTR(profession, military, unit->profession)) + { add_work_history(unit, JOB_MILITARY); + continue; + } if (!unit->job.current_job) { @@ -986,6 +1249,24 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector class ListEntry { public: - T *elem; + T elem; string text; bool selected; - ListEntry(string text, T &elem) : elem(&elem), text(text), selected(false) + ListEntry(const string text, const T elem) : elem(elem), text(text), selected(false) { } }; @@ -158,6 +158,7 @@ class ListColumn public: int highlighted_index; int display_start_offset; + unsigned short text_clip_at; int32_t bottom_margin, search_margin, left_margin; bool multiselect; bool allow_null; @@ -173,6 +174,7 @@ public: left_margin = 2; search_margin = 63; highlighted_index = 0; + text_clip_at = 0; multiselect = false; allow_null = true; auto_select = false; @@ -202,7 +204,7 @@ public: max_item_width = entry.text.length(); } - void add(const string &text, T &elem) + void add(const string &text, const T &elem) { list.push_back(ListEntry(text, elem)); if (text.length() > max_item_width) @@ -211,6 +213,9 @@ public: int fixWidth() { + if (text_clip_at > 0 && max_item_width > text_clip_at) + max_item_width = text_clip_at; + for (auto it = list.begin(); it != list.end(); it++) { it->text = pad_string(it->text, max_item_width, false); @@ -232,9 +237,14 @@ public: ++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); + + string item_label = display_list[i]->text; + if (text_clip_at > 0 && item_label.length() > text_clip_at) + item_label.resize(text_clip_at); + + paint_text(fg_color, left_margin, y, item_label, bg_color); int x = left_margin + display_list[i]->text.length() + 1; - display_extras(*display_list[i]->elem, x, y); + display_extras(display_list[i]->elem, x, y); } if (is_selected_column && allow_search) @@ -355,9 +365,9 @@ public: entry->selected = !entry->selected; } - vector getSelectedElems(bool only_one = false) + vector getSelectedElems(bool only_one = false) { - vector results; + vector results; for (auto it = list.begin(); it != list.end(); it++) { if ((*it).selected) @@ -371,17 +381,22 @@ public: return results; } + T getFirstSelectedElem() + { + vector results = getSelectedElems(true); + if (results.size() == 0) + return nullptr; + else + return results[0]; + } + void clearSelection() { for_each_(list, [] (ListEntry &e) { e.selected = false; }); } - void selectItem(const T* elem) + void selectItem(const T elem) { - if (!elem) - return; - - //clearSelection(); int i = 0; for (; i < display_list.size(); i++) { @@ -399,15 +414,6 @@ public: filterDisplay(); } - T* getFirstSelectedElem() - { - vector results = getSelectedElems(true); - if (results.size() == 0) - return nullptr; - else - return results[0]; - } - size_t getDisplayListSize() { return display_list.size(); From 8e9fc235a9072b8adfedd32557dee94acadadd53 Mon Sep 17 00:00:00 2001 From: falconne Date: Sun, 10 Mar 2013 16:14:00 +1300 Subject: [PATCH 44/73] Adding a debug mode to track down an elusive bug. --- plugins/buildingplan.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index a1c3041e8..6002aa064 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -37,7 +37,7 @@ using df::global::ui_build_selector; using df::global::world; DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.5 +#define PLUGIN_VERSION 0.6 #ifndef HAVE_NULLPTR #define nullptr 0L @@ -80,6 +80,16 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) #define MAX_MATERIAL 21 #define SIDEBAR_WIDTH 30 +static bool show_debugging = false; + +static void debug(const string &msg) +{ + if (!show_debugging) + return; + + color_ostream_proxy out(Core::getInstance().getConsole()); + out << "DEBUG (buildingplan): " << msg << endl; +} /* * Material Choice Screen @@ -520,6 +530,7 @@ public: if (closest_distance > -1 && assignItem(*closest_item)) { + debug("Item assigned"); items_vector->erase(closest_item); remove(); return true; @@ -688,23 +699,30 @@ public: void doCycle() { + debug("Running Cycle"); if (planned_buildings.size() == 0) return; + debug("Planned count: " + int_to_string(planned_buildings.size())); + gather_available_items(); for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) { if (building_iter->isValid()) { + if (show_debugging) + debug(string("Trying to allocate ") + enum_item_key_str(building_iter->getType())); + auto required_item_type = item_for_building_type[building_iter->getType()]; auto items_vector = &available_item_vectors[required_item_type]; if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector)) { + debug("Unable to allocate an item"); ++building_iter; continue; } } - + debug("Removing building plan"); building_iter = planned_buildings.erase(building_iter); } } @@ -804,6 +822,7 @@ private: void gather_available_items() { + debug("Gather available items"); for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++) { iter->second.clear(); @@ -1110,7 +1129,15 @@ static command_result buildingplan_cmd(color_ostream &out, vector & par { if (!parameters.empty()) { - out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl; + if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v') + { + out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl; + } + else if (parameters.size() == 2 && toLower(parameters[0]) == "debug") + { + show_debugging = (toLower(parameters[1]) == "on"); + out << "Debugging " << ((show_debugging) ? "enabled" : "disabled") << endl; + } } return CR_OK; From bc0c493b7c2938ad18e63f46b8f830a9ac2fd109 Mon Sep 17 00:00:00 2001 From: falconne Date: Tue, 12 Mar 2013 21:17:22 +1300 Subject: [PATCH 45/73] Workaround for strange crash in accelerated mod --- plugins/buildingplan.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 6002aa064..4d8d48cf0 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -37,7 +37,7 @@ using df::global::ui_build_selector; using df::global::world; DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.6 +#define PLUGIN_VERSION 0.7 #ifndef HAVE_NULLPTR #define nullptr 0L @@ -550,8 +550,12 @@ public: ref->building_id = building->id; + if (building->jobs.size() != 1) + return false; + auto job = building->jobs[0]; - delete job->job_items[0]; + + for_each_(job->job_items, [] (df::job_item *x) { delete x; }); job->job_items.clear(); job->flags.bits.suspend = false; From 215a2e78af45a5c62e6773c452a65b4c42f8b29b Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Wed, 13 Mar 2013 21:44:33 +1300 Subject: [PATCH 46/73] Fix crash when persistent data refers to missing buildings. Can only happen if planned buildings are removed with the plugin removed, then the game is reloaded after the plugin is put back. --- plugins/buildingplan.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 4d8d48cf0..a210a0278 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -37,7 +37,7 @@ using df::global::ui_build_selector; using df::global::world; DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.7 +#define PLUGIN_VERSION 0.8 #ifndef HAVE_NULLPTR #define nullptr 0L @@ -496,6 +496,9 @@ public: } building = df::building::find(config.ival(1)); + if (!building) + return; + pos = df::coord(building->centerx, building->centery, building->z); filter.min_quality = static_cast(config.ival(2) - 1); filter.decorated_only = config.ival(3) - 1; From b5806f7278e5312d6a87fe040a85a1edfa3b7994 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Wed, 13 Mar 2013 22:21:30 +1300 Subject: [PATCH 47/73] Copy fixes from r3 to fix search issues in Nobles screen. --- plugins/search.cpp | 86 ++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 8d0805947..dbf3ade3f 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -91,7 +91,15 @@ public: return false; if (!can_init(screen)) + { + if (is_valid()) + { + clear_search(); + reset_all(); + } + return false; + } if (!is_valid()) { @@ -296,6 +304,11 @@ protected: return true; } + virtual bool force_in_search(size_t index) + { + return false; + } + // The actual sort virtual void do_search() { @@ -316,6 +329,12 @@ protected: string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) { + if (force_in_search(i)) + { + add_to_filtered_list(i); + continue; + } + if (!is_valid_for_search(i)) continue; @@ -383,15 +402,7 @@ protected: { auto list = getLayerList(screen); if (!list->active) - { - if (this->is_valid()) - { - this->clear_search(); - this->reset_all(); - } - return false; - } return true; } @@ -655,6 +666,10 @@ template V generic_search_hook ::module; template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) +#define IMPLEMENT_HOOKS_PRIO(screen, module, prio) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, 100); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, 100) // // END: Generic Search functionality @@ -799,7 +814,8 @@ IMPLEMENT_HOOKS(df::viewscreen_petst, pets_search); // // START: Stocks screen search // -class stocks_search : public search_generic +typedef search_generic stocks_search_base; +class stocks_search : public stocks_search_base { public: @@ -833,7 +849,7 @@ public: return false; } - return search_generic::process_input(input); + return stocks_search_base::process_input(input); } virtual void do_post_input_feed() @@ -883,7 +899,8 @@ IMPLEMENT_HOOKS(df::viewscreen_storesst, stocks_search); // // START: Unit screen search // -class unitlist_search : public search_twocolumn_modifiable +typedef search_twocolumn_modifiable unitlist_search_base; +class unitlist_search : public unitlist_search_base { public: void render() const @@ -894,7 +911,7 @@ public: private: void do_post_init() { - search_twocolumn_modifiable::do_post_init(); + unitlist_search_base::do_post_init(); read_only = true; } @@ -969,8 +986,8 @@ private: } }; -typedef generic_search_hook unitlist_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +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 @@ -1143,7 +1160,8 @@ IMPLEMENT_HOOKS(df::viewscreen_layer_stockpilest, stockpile_search); // // START: Military screen search // -class military_search : public layered_search +typedef layered_search military_search_base; +class military_search : public military_search_base { public: @@ -1167,7 +1185,7 @@ public: if (screen->page != df::viewscreen_layer_militaryst::Positions) return false; - return layered_search::can_init(screen); + return military_search_base::can_init(screen); } vector *get_primary_list() @@ -1197,7 +1215,7 @@ public: } }; -IMPLEMENT_HOOKS(df::viewscreen_layer_militaryst, military_search); +IMPLEMENT_HOOKS_PRIO(df::viewscreen_layer_militaryst, military_search, 100); // // END: Military screen search @@ -1209,8 +1227,8 @@ IMPLEMENT_HOOKS(df::viewscreen_layer_militaryst, military_search); // static map< df::building_type, vector > room_quality_names; static int32_t room_value_bounds[] = {1, 100, 250, 500, 1000, 1500, 2500, 10000}; - -class roomlist_search : public search_twocolumn_modifiable +typedef search_twocolumn_modifiable roomlist_search_base; +class roomlist_search : public roomlist_search_base { public: void render() const @@ -1221,7 +1239,7 @@ public: private: void do_post_init() { - search_twocolumn_modifiable::do_post_init(); + roomlist_search_base::do_post_init(); read_only = true; } @@ -1292,7 +1310,7 @@ IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search); // // START: Announcement list search // -class annoucnement_search : public search_generic +class annoucnement_search : public search_generic { public: void render() const @@ -1331,8 +1349,8 @@ IMPLEMENT_HOOKS(df::viewscreen_announcelistst, annoucnement_search); // START: Nobles search list // typedef df::viewscreen_layer_noblelistst::T_candidates T_candidates; - -class nobles_search : public layered_search +typedef layered_search nobles_search_base; +class nobles_search : public nobles_search_base { public: @@ -1349,12 +1367,17 @@ public: print_search_option(2, 23); } + bool force_in_search(size_t index) + { + return index == 0; // Leave Vacant + } + bool can_init(df::viewscreen_layer_noblelistst *screen) { if (screen->mode != df::viewscreen_layer_noblelistst::Appoint) return false; - return layered_search::can_init(screen); + return nobles_search_base::can_init(screen); } vector *get_primary_list() @@ -1419,7 +1442,8 @@ void get_job_details(string &desc, df::job *job) desc += "suspended."; } -class joblist_search : public search_twocolumn_modifiable +typedef search_twocolumn_modifiable joblist_search_base; +class joblist_search : public joblist_search_base { public: void render() const @@ -1430,7 +1454,7 @@ public: private: void do_post_init() { - search_twocolumn_modifiable::do_post_init(); + joblist_search_base::do_post_init(); read_only = true; } @@ -1479,19 +1503,15 @@ IMPLEMENT_HOOKS(df::viewscreen_joblistst, joblist_search); // using df::global::ui; -class burrow_search : public search_twocolumn_modifiable +typedef search_twocolumn_modifiable burrow_search_base; +class burrow_search : public burrow_search_base { public: bool can_init(df::viewscreen_dwarfmodest *screen) { if (ui->main.mode == df::ui_sidebar_mode::Burrows && ui->burrows.in_add_units_mode) { - return search_twocolumn_modifiable::can_init(screen); - } - else if (is_valid()) - { - clear_search(); - reset_all(); + return burrow_search_base::can_init(screen); } return false; From 5a92eabb6b5001cdb6c175cde1605fdce8437e65 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 15 Mar 2013 23:50:29 +1300 Subject: [PATCH 48/73] Add all fortress dwarves to happiness monitor. --- plugins/dwarfmonitor.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index eb0d01c30..46522e74a 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -152,10 +152,10 @@ public: selected_column = 0; auto last_selected_index = dwarf_activity_column.highlighted_index; - dwarf_activity_column.clear(); + dwarves_column.clear(); dwarf_activity_values.clear(); - for (auto it = work_history.begin(); it != work_history.end(); it++) + for (auto it = work_history.begin(); it != work_history.end();) { auto unit = it->first; if (Units::isDead(unit)) @@ -1037,15 +1037,7 @@ static void update_dwarf_stats(bool is_paused) if (!Units::isCitizen(unit)) continue; - if (unit->profession == profession::BABY || - unit->profession == profession::CHILD || - unit->profession == profession::DRUNK) - { - continue; - } - - using namespace DFHack::Units; - if (!isSane(unit) || isDead(unit)) + if (DFHack::Units::isDead(unit)) { auto it = work_history.find(unit); if (it != work_history.end()) @@ -1063,11 +1055,11 @@ static void update_dwarf_stats(bool is_paused) misery[1]++; else if (happy <= 50) // unhappy misery[2]++; - else if (happy <= 75) // fine + else if (happy <= 75) // fine misery[3]++; - else if (happy <= 125) // quite content + else if (happy <= 125) // quite content misery[4]++; - else if (happy <= 150) // happy + else if (happy <= 150) // happy misery[5]++; else // ecstatic misery[6]++; @@ -1076,6 +1068,13 @@ static void update_dwarf_stats(bool is_paused) if (!monitor_jobs || is_paused) continue; + if (unit->profession == profession::BABY || + unit->profession == profession::CHILD || + unit->profession == profession::DRUNK) + { + continue; + } + if (ENUM_ATTR(profession, military, unit->profession)) { add_work_history(unit, JOB_MILITARY); From f69f24573269d09556663c9500045ffd7f5e72b8 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 16 Mar 2013 12:26:37 +1300 Subject: [PATCH 49/73] Allow planned constructions orthogonal to stairs --- plugins/automaterial.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 712565ac0..3c38df215 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -439,7 +439,9 @@ static bool is_valid_building_site(building_site &site, bool orthogonal_check, b } else if (orthogonal_check) { - if (shape != tiletype_shape::RAMP && shape_basic != tiletype_shape_basic::Floor) + if (shape != tiletype_shape::RAMP && + shape_basic != tiletype_shape_basic::Floor && + shape_basic != tiletype_shape_basic::Stair) return false; } else From c16bd3d71a5bd380c08d0a01fdd9e2b38c8527da Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 16 Mar 2013 18:25:02 +1300 Subject: [PATCH 50/73] Apply r3 name changes --- library/xml | 2 +- plugins/buildingplan.cpp | 13 ++++++------- plugins/dwarfmonitor.cpp | 2 +- plugins/resume.cpp | 3 +-- plugins/zone.cpp | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/library/xml b/library/xml index 2f76de54d..4d2afc3a0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2f76de54dacd32af567b177adfb9d037fdf62d9b +Subproject commit 4d2afc3a0bcebdb17415dc2827b44fd35986a368 diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index a210a0278..02a1d3a9d 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -478,7 +478,7 @@ public: this->building = building; this->filter = *filter; pos = df::coord(building->centerx, building->centery, building->z); - config = Core::getInstance().getWorld()->AddPersistentData("buildingplan/constraints"); + config = DFHack::World::AddPersistentData("buildingplan/constraints"); config.val() = filter->getMaterialFilterAsSerial(); config.ival(1) = building->id; config.ival(2) = filter->min_quality + 1; @@ -606,7 +606,7 @@ public: void remove() { - Core::getInstance().getWorld()->DeletePersistentData(config); + DFHack::World::DeletePersistentData(config); } private: @@ -637,9 +637,8 @@ public: void reset(color_ostream &out) { planned_buildings.clear(); - auto pworld = Core::getInstance().getWorld(); std::vector items; - pworld->GetPersistentData(&items, "buildingplan/constraints"); + DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); for (auto i = items.begin(); i != items.end(); i++) { @@ -842,10 +841,10 @@ private: #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); + F(in_building); F(construction); F(artifact); #undef F - std::vector &items = world->items.other[items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::IN_PLAY]; for (size_t i = 0; i < items.size(); i++) { @@ -861,7 +860,7 @@ private: if (itype == item_type::BOX && item->isBag()) continue; //Skip bags - if (item->flags.bits.artifact1 || item->flags.bits.artifact2) + if (item->flags.bits.artifact) continue; if (item->flags.bits.in_job || diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 46522e74a..37e7c1860 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1107,7 +1107,7 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) static decltype(world->frame_counter) last_frame_count = 0; - bool is_paused = Core::getInstance().getWorld()->ReadPauseState(); + bool is_paused = DFHack::World::ReadPauseState(); if (is_paused) { if (monitor_misery && !misery_upto_date) diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 8920b9ce5..cf02f26d6 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -220,8 +220,7 @@ struct resume_hook : public df::viewscreen_dwarfmodest { INTERPOSE_NEXT(render)(); - DFHack::World *world = Core::getInstance().getWorld(); - if (enabled && world->ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default) + if (enabled && DFHack::World::ReadPauseState() && ui->main.mode == ui_sidebar_mode::Default) { scan_for_suspended_buildings(); show_suspended_buildings(); diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 0f8abc397..3195cffda 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -1005,9 +1005,9 @@ bool isAssigned(df::unit* unit) bool isAssignedToZone(df::unit* unit) { bool assigned = false; - for (size_t r=0; r < unit->refs.size(); r++) + for (size_t r=0; r < unit->general_refs.size(); r++) { - df::general_ref * ref = unit->refs[r]; + df::general_ref * ref = unit->general_refs[r]; auto rtype = ref->getType(); if(rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) { From 4bd5e3db77f1013519f1499c8230e33134994c6f Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 16 Mar 2013 20:35:53 +1300 Subject: [PATCH 51/73] Add mousequery to build --- plugins/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 700593f9a..348ab1973 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -140,6 +140,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(buildingplan buildingplan.cpp) DFHACK_PLUGIN(resume resume.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp) + DFHACK_PLUGIN(mousequery mousequery.cpp) endif() From 59ec9bfc570fc7a532bf8c2d876eda0b8bfc9592 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 16 Mar 2013 22:13:01 +1300 Subject: [PATCH 52/73] Update versions --- plugins/buildingplan.cpp | 2 +- plugins/dwarfmonitor.cpp | 2 +- plugins/resume.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 02a1d3a9d..a11798de4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -37,7 +37,7 @@ using df::global::ui_build_selector; using df::global::world; DFHACK_PLUGIN("buildingplan"); -#define PLUGIN_VERSION 0.8 +#define PLUGIN_VERSION 0.9 #ifndef HAVE_NULLPTR #define nullptr 0L diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 37e7c1860..1095d24b9 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -25,7 +25,7 @@ using df::global::ui; typedef int16_t activity_type; -#define PLUGIN_VERSION 0.4 +#define PLUGIN_VERSION 0.5 #define DAY_TICKS 1200 #define DELTA_TICKS 100 diff --git a/plugins/resume.cpp b/plugins/resume.cpp index cf02f26d6..e697a133f 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -39,7 +39,7 @@ using df::global::ui; using df::global::world; DFHACK_PLUGIN("resume"); -#define PLUGIN_VERSION 0.1 +#define PLUGIN_VERSION 0.2 #ifndef HAVE_NULLPTR #define nullptr 0L From dc9a62fd1d8b8f9ba87253b45683265ffce07e41 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 22 Mar 2013 23:08:33 +1300 Subject: [PATCH 53/73] Fix crash when using right mouse button --- plugins/mousequery.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 21215ff54..dfb89f6f4 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -219,10 +219,9 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest { while (ui->main.mode != Default) { + enabler->mouse_rbut = 0; send_key(df::interface_key::LEAVESCREEN); } - - enabler->mouse_rbut = 0; } } } From 323c56a641ea0c72f3f1bd112e32f3d6a0ef3d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rnar=20Hansen?= Date: Fri, 29 Mar 2013 15:03:09 +0100 Subject: [PATCH 54/73] Right-align happiness monitor overlay. The overlay was blocking game messages which are displayed on the left side. --- plugins/dwarfmonitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 1095d24b9..6fe89531a 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1143,7 +1143,7 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest if (monitor_misery && Maps::IsValid()) { - int x = 1; + int x = gps->dimx - 22; int y = gps->dimy - 1; OutputString(COLOR_WHITE, x, y, "H:"); OutputString(COLOR_LIGHTRED, x, y, int_to_string(misery[0])); From edc305db690f226eddd59e8b11ee66cb3510d571 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 25 Mar 2013 23:47:05 +1300 Subject: [PATCH 55/73] AutoTrade plugin: Automatically send items in marked stockpiles to trade depot, when trading is possible. --- plugins/CMakeLists.txt | 1 + plugins/autotrade.cpp | 576 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 577 insertions(+) create mode 100644 plugins/autotrade.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 348ab1973..5529ab8a2 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -141,6 +141,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(resume resume.cpp) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) + DFHACK_PLUGIN(autotrade autotrade.cpp) endif() diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp new file mode 100644 index 000000000..2cc494fd0 --- /dev/null +++ b/plugins/autotrade.cpp @@ -0,0 +1,576 @@ +#include "uicommon.h" + +#include "modules/Gui.h" + +#include "df/world.h" +#include "df/world_raws.h" +#include "df/building_def.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/building_stockpilest.h" +#include "modules/Items.h" +#include "df/building_tradedepotst.h" +#include "df/general_ref_building_holderst.h" +#include "df/job.h" +#include "df/job_item_ref.h" +#include "modules/Job.h" +#include "df/ui.h" +#include "df/caravan_state.h" +#include "modules/Maps.h" +#include "modules/World.h" + +using df::global::world; +using df::global::cursor; +using df::global::ui; +using df::building_stockpilest; + +DFHACK_PLUGIN("autotrade"); +#define PLUGIN_VERSION 0.1 + + +/* + * Stockpile Access + */ + +static building_stockpilest *get_selected_stockpile() +{ + if (!Gui::dwarfmode_hotkey(Core::getTopViewscreen()) || + ui->main.mode != ui_sidebar_mode::QueryBuilding) + { + return nullptr; + } + + return virtual_cast(world->selected_building); +} + +static bool can_trade() +{ + if (df::global::ui->caravans.size() == 0) + return false; + + for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++) + { + auto caravan = *it; + auto trade_state = caravan->trade_state; + auto time_remaining = caravan->time_remaining; + if ((trade_state != 1 && trade_state != 2) || time_remaining == 0) + return false; + } + + return true; +} + +class StockpileInfo { +public: + + StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) + { + readBuilding(); + } + + StockpileInfo(PersistentDataItem &config) + { + this->config = config; + id = config.ival(1); + } + + bool inStockpile(df::item *i) + { + df::item *container = Items::getContainer(i); + if (container) + return inStockpile(container); + + if (i->pos.z != z) return false; + if (i->pos.x < x1 || i->pos.x >= x2 || + i->pos.y < y1 || i->pos.y >= y2) return false; + int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; + return sp->room.extents[e] == 1; + } + + bool isValid() + { + auto found = df::building::find(id); + return found && found == sp && found->getType() == building_type::Stockpile; + } + + bool load() + { + auto found = df::building::find(id); + if (!found || found->getType() != building_type::Stockpile) + return false; + + sp = virtual_cast(found); + if (!sp) + return false; + + readBuilding(); + + return true; + } + + int32_t getId() + { + return id; + } + + bool matches(df::building_stockpilest* sp) + { + return this->sp == sp; + } + + void save() + { + config = DFHack::World::AddPersistentData("autotrade/stockpiles"); + config.ival(1) = id; + } + + void remove() + { + DFHack::World::DeletePersistentData(config); + } + +private: + PersistentDataItem config; + df::building_stockpilest* sp; + int x1, x2, y1, y2, z; + int32_t id; + + void readBuilding() + { + id = sp->id; + z = sp->z; + x1 = sp->room.x; + x2 = sp->room.x + sp->room.width; + y1 = sp->room.y; + y2 = sp->room.y + sp->room.height; + } +}; + + +/* + * Depot Access + */ + +class TradeDepotInfo +{ +public: + TradeDepotInfo() : depot(0) + { + + } + + bool findDepot() + { + if (isValid()) + return true; + + reset(); + for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++) + { + auto bld = *bld_it; + if (!isUsableDepot(bld)) + continue; + + depot = bld; + id = depot->id; + break; + } + + return depot; + } + + bool assignItem(df::item *item) + { + auto href = df::allocate(); + if (!href) + return false; + + auto job = new df::job(); + + df::coord tpos(depot->centerx, depot->centery, depot->z); + job->pos = tpos; + + job->job_type = job_type::BringItemToDepot; + + // job <-> item link + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + return false; + } + + // job <-> building link + href->building_id = id; + depot->jobs.push_back(job); + job->general_refs.push_back(href); + + // add to job list + Job::linkIntoWorld(job); + + return true; + } + + bool reset() + { + depot = 0; + } + +private: + int32_t id; + df::building *depot; + + bool isUsableDepot(df::building* bld) + { + if (bld->getType() != building_type::TradeDepot) + return false; + + if (bld->getBuildStage() < bld->getMaxBuildStage()) + return false; + + if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding) + return false; + + return true; + } + + bool isValid() + { + if (!depot) + return false; + + auto found = df::building::find(id); + return found && found == depot && isUsableDepot(found); + } + +}; + +static TradeDepotInfo depot_info; + + +/* + * Item Manipulation + */ + +static bool is_valid_item(df::item *item) +{ + for (size_t i = 0; i < item->general_refs.size(); i++) + { + df::general_ref *ref = item->general_refs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + return false; + + case general_ref_type::UNIT_HOLDER: + return false; + + case general_ref_type::BUILDING_HOLDER: + return false; + + default: + break; + } + } + + for (size_t i = 0; i < item->specific_refs.size(); i++) + { + df::specific_ref *ref = item->specific_refs[i]; + + if (ref->type == specific_ref_type::JOB) + { + // Ignore any items assigned to a job + return false; + } + } + + + return true; +} + +static void mark_all_in_stockpiles(vector &stockpiles, bool announce) +{ + if (!depot_info.findDepot()) + { + if (announce) + Gui::showAnnouncement("Cannot trade, no valid depot available", COLOR_RED, true); + + return; + } + + std::vector &items = world->items.other[items_other_id::IN_PLAY]; + + //FIXME filter out mandates + + // Precompute a bitmask with the bad flags + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); + F(spider_web); F(owned); F(in_job); +#undef F + + size_t marked_count = 0; + size_t error_count = 0; + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + if (!is_valid_item(item)) + continue; + + for (auto it = stockpiles.begin(); it != stockpiles.end(); it++) + { + if (!it->inStockpile(item)) + continue; + + if (depot_info.assignItem(item)) + { + ++marked_count; + } + else + { + if (++error_count < 5) + { + Gui::showZoomAnnouncement(df::announcement_type::CANCEL_JOB, item->pos, + "Cannot trade item from stockpile " + int_to_string(it->getId()), COLOR_RED, true); + } + } + } + } + + if (marked_count) + Gui::showAnnouncement("Marked " + int_to_string(marked_count) + " items for trade", COLOR_GREEN, false); + else if (announce) + Gui::showAnnouncement("No more items to mark", COLOR_RED, true); + + if (error_count >= 5) + { + Gui::showAnnouncement(int_to_string(error_count) + " items were not marked", COLOR_RED, true); + } +} + + +/* + * Stockpile Monitoring + */ + +class StockpileMonitor +{ +public: + bool isMonitored(df::building_stockpilest *sp) + { + for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++) + { + if (it->matches(sp)) + return true; + } + + return false; + } + + void add(df::building_stockpilest *sp) + { + auto pile = StockpileInfo(sp); + if (pile.isValid()) + { + monitored_stockpiles.push_back(StockpileInfo(sp)); + monitored_stockpiles.back().save(); + } + } + + void remove(df::building_stockpilest *sp) + { + for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end(); it++) + { + if (it->matches(sp)) + { + it->remove(); + monitored_stockpiles.erase(it); + break; + } + } + } + + void doCycle() + { + if (!can_trade()) + return; + + for (auto it = monitored_stockpiles.begin(); it != monitored_stockpiles.end();) + { + if (!it->isValid()) + { + it = monitored_stockpiles.erase(it); + continue; + } + + ++it; + } + + mark_all_in_stockpiles(monitored_stockpiles, false); + } + + void reset() + { + monitored_stockpiles.clear(); + std::vector items; + DFHack::World::GetPersistentData(&items, "autotrade/stockpiles"); + + for (auto i = items.begin(); i != items.end(); i++) + { + auto pile = StockpileInfo(*i); + if (pile.load()) + monitored_stockpiles.push_back(StockpileInfo(pile)); + else + pile.remove(); + } + } + + +private: + vector monitored_stockpiles; +}; + +static StockpileMonitor monitor; + +#define DELTA_TICKS 600 + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + if(!Maps::IsValid()) + return CR_OK; + + static decltype(world->frame_counter) last_frame_count = 0; + + if (DFHack::World::ReadPauseState()) + return CR_OK; + + if (world->frame_counter - last_frame_count < DELTA_TICKS) + return CR_OK; + + last_frame_count = world->frame_counter; + + monitor.doCycle(); + + return CR_OK; +} + + +/* + * Interface + */ + +struct trade_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + bool handleInput(set *input) + { + building_stockpilest *sp = get_selected_stockpile(); + if (!sp) + return false; + + if (input->count(interface_key::CUSTOM_M)) + { + if (!can_trade()) + return false; + + vector wrapper; + wrapper.push_back(StockpileInfo(sp)); + mark_all_in_stockpiles(wrapper, true); + + return true; + } + else if (input->count(interface_key::CUSTOM_U)) + { + if (monitor.isMonitored(sp)) + monitor.remove(sp); + else + monitor.add(sp); + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!handleInput(input)) + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + building_stockpilest *sp = get_selected_stockpile(); + if (!sp) + return; + + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 23; + + if (can_trade()) + OutputHotkeyString(x, y, "Mark all for trade", "m", true, left_margin); + + OutputToggleString(x, y, "Auto trade", "u", monitor.isMonitored(sp), true, left_margin); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(trade_hook, render); + +static command_result autotrade_cmd(color_ostream &out, vector & parameters) +{ + if (!parameters.empty()) + { + if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v') + { + out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl; + } + } + + return CR_OK; +} + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) + { + case DFHack::SC_MAP_LOADED: + depot_info.reset(); + monitor.reset(); + break; + case DFHack::SC_MAP_UNLOADED: + break; + default: + break; + } + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (!gps || !INTERPOSE_HOOK(trade_hook, feed).apply() || !INTERPOSE_HOOK(trade_hook, render).apply()) + out.printerr("Could not insert autotrade hooks!\n"); + + commands.push_back( + PluginCommand( + "autotrade", "Automatically send items in marked stockpiles to trade depot, when trading is possible.", + autotrade_cmd, false, "")); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} From a5b98482710c88b3320ddc3889d7732ea5e2a16a Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 30 Mar 2013 16:38:21 +1300 Subject: [PATCH 56/73] Clean up --- plugins/buildingplan.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index a11798de4..3c69d1fc0 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -39,10 +39,6 @@ using df::global::world; DFHACK_PLUGIN("buildingplan"); #define PLUGIN_VERSION 0.9 -#ifndef HAVE_NULLPTR -#define nullptr 0L -#endif - struct MaterialDescriptor { df::item_type item_type; @@ -1159,7 +1155,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sun, 31 Mar 2013 23:25:57 +1300 Subject: [PATCH 57/73] Respect export mandates --- plugins/autotrade.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index 2cc494fd0..d289625b3 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -15,6 +15,7 @@ #include "modules/Job.h" #include "df/ui.h" #include "df/caravan_state.h" +#include "df/mandate.h" #include "modules/Maps.h" #include "modules/World.h" @@ -251,6 +252,31 @@ static TradeDepotInfo depot_info; * Item Manipulation */ +static bool check_mandates(df::item *item) +{ + for (auto it = world->mandates.begin(); it != world->mandates.end(); it++) + { + auto mandate = *it; + + if (mandate->mode != 0) + continue; + + if (item->getType() != mandate->item_type || + (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) + continue; + + if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) + continue; + + if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) + continue; + + return false; + } + + return true; +} + static bool is_valid_item(df::item *item) { for (size_t i = 0; i < item->general_refs.size(); i++) @@ -284,6 +310,8 @@ static bool is_valid_item(df::item *item) } } + if (!check_mandates(item)) + return false; return true; } @@ -300,7 +328,6 @@ static void mark_all_in_stockpiles(vector &stockpiles, bool annou std::vector &items = world->items.other[items_other_id::IN_PLAY]; - //FIXME filter out mandates // Precompute a bitmask with the bad flags df::item_flags bad_flags; @@ -329,6 +356,22 @@ static void mark_all_in_stockpiles(vector &stockpiles, bool annou if (!it->inStockpile(item)) continue; + // In case of container, check contained items for mandates + bool mandates_ok = true; + vector contained_items; + Items::getContainedItems(item, &contained_items); + for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++) + { + if (!check_mandates(*cit)) + { + mandates_ok = false; + break; + } + } + + if (!mandates_ok) + continue; + if (depot_info.assignItem(item)) { ++marked_count; From 1550df496fcdb59606c6db58d737e5c28d49d5a7 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 1 Apr 2013 19:52:13 +1300 Subject: [PATCH 58/73] Right align happiness monitor to avoid interference with announcements --- plugins/dwarfmonitor.cpp | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 6fe89531a..6ff62253a 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1128,6 +1128,17 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) return CR_OK; } +static color_value monitor_colors[] = +{ + COLOR_LIGHTRED, + COLOR_RED, + COLOR_YELLOW, + COLOR_WHITE, + COLOR_CYAN, + COLOR_LIGHTBLUE, + COLOR_LIGHTGREEN +}; + struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -1143,22 +1154,23 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest if (monitor_misery && Maps::IsValid()) { - int x = gps->dimx - 22; + string entries[7]; + size_t length = 9; + for (int i = 0; i < 7; i++) + { + entries[i] = int_to_string(misery[i]); + length += entries[i].length(); + } + + int x = gps->dimx - length; int y = gps->dimy - 1; OutputString(COLOR_WHITE, x, y, "H:"); - OutputString(COLOR_LIGHTRED, x, y, int_to_string(misery[0])); - OutputString(COLOR_WHITE, x, y, "/"); - OutputString(COLOR_RED, x, y, int_to_string(misery[1])); - OutputString(COLOR_WHITE, x, y, "/"); - OutputString(COLOR_YELLOW, x, y, int_to_string(misery[2])); - OutputString(COLOR_WHITE, x, y, "/"); - OutputString(COLOR_WHITE, x, y, int_to_string(misery[3])); - OutputString(COLOR_WHITE, x, y, "/"); - OutputString(COLOR_CYAN, x, y, int_to_string(misery[4])); - OutputString(COLOR_WHITE, x, y, "/"); - OutputString(COLOR_LIGHTBLUE, x, y, int_to_string(misery[5])); - OutputString(COLOR_WHITE, x, y, "/"); - OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(misery[6])); + for (int i = 0; i < 7; i++) + { + OutputString(monitor_colors[i], x, y, entries[i]); + if (i < 6) + OutputString(COLOR_WHITE, x, y, "/"); + } } } }; From 1070eec0160f2d2efde921c551b373b57fc3d0fa Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 1 Apr 2013 19:52:34 +1300 Subject: [PATCH 59/73] Make planned doors pet passable --- plugins/buildingplan.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 3c69d1fc0..810314bc1 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -31,6 +31,7 @@ #include "df/buildings_other_id.h" #include "modules/World.h" #include "df/building.h" +#include "df/building_doorst.h" using df::global::ui; using df::global::ui_build_selector; @@ -757,6 +758,13 @@ public: (*iter)->flags.bits.suspend = true; } + if (type == building_type::Door) + { + auto door = virtual_cast(newinst); + if (door) + door->door_flags.bits.pet_passable = true; + } + addPlannedBuilding(newinst); return true; From d7c91975c0a1137de0040507f23e271084077461 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 1 Apr 2013 20:02:39 +1300 Subject: [PATCH 60/73] Fix warnings --- plugins/autotrade.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index d289625b3..be9615ef0 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -211,7 +211,7 @@ public: return true; } - bool reset() + void reset() { depot = 0; } From 9d6f202988ea93c3ed97513cd336cbcc1feee365 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Apr 2013 12:03:37 +0400 Subject: [PATCH 61/73] Fix the search plugin crashing the military screen. The cause of the crash is that the right list is used for multiple different things in different pages, so when cleaning up after a page switch it is not acceptable to mess with it in any way. However the search plugin changed its length, thus causing a crash if the new contents were shorter than the original candidate list. --- plugins/search.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 4e502b838..541fc9506 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -422,12 +422,17 @@ protected: virtual bool can_init(S *screen) { auto list = getLayerList(screen); - if (!list->active) + if (!is_list_valid(screen) || !list->active) return false; return true; } + virtual bool is_list_valid(S*) + { + return true; + } + virtual void do_search() { search_generic::do_search(); @@ -444,8 +449,12 @@ protected: virtual void clear_search() { search_generic::clear_search(); - auto list = getLayerList(this->viewscreen); - list->num_entries = this->get_primary_list()->size(); + + if (is_list_valid(this->viewscreen)) + { + auto list = getLayerList(this->viewscreen); + list->num_entries = this->get_primary_list()->size(); + } } private: @@ -1208,12 +1217,14 @@ public: return 'q'; } - bool can_init(df::viewscreen_layer_militaryst *screen) + // When not on the positions page, this list is used for something + // else entirely, so screwing with it seriously breaks stuff. + bool is_list_valid(df::viewscreen_layer_militaryst *screen) { if (screen->page != df::viewscreen_layer_militaryst::Positions) return false; - return military_search_base::can_init(screen); + return true; } vector *get_primary_list() From efaa247d2a3336588c9ff024381d393e96b684ae Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 6 Apr 2013 17:40:07 +1300 Subject: [PATCH 62/73] Stocks start --- plugins/CMakeLists.txt | 1 + plugins/stocks.cpp | 206 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 plugins/stocks.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 5529ab8a2..c21e50434 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -142,6 +142,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(autotrade autotrade.cpp) + DFHACK_PLUGIN(stocks stocks.cpp) endif() diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp new file mode 100644 index 000000000..93966c90a --- /dev/null +++ b/plugins/stocks.cpp @@ -0,0 +1,206 @@ +#include "uicommon.h" + +#include + +// DF data structure definition headers +#include "DataDefs.h" +#include "Types.h" +#include "df/item.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/items_other_id.h" +#include "df/job.h" +#include "df/world.h" + +#include "modules/Gui.h" +#include "modules/Items.h" +#include "modules/Job.h" +#include "modules/World.h" + +using df::global::world; + +DFHACK_PLUGIN("stocks"); +#define PLUGIN_VERSION 0.1 + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +#define SIDEBAR_WIDTH 30 + +static bool show_debugging = false; + +static void debug(const string &msg) +{ + if (!show_debugging) + return; + + color_ostream_proxy out(Core::getInstance().getConsole()); + out << "DEBUG (stocks): " << msg << endl; +} + + +class ViewscreenStocks : public dfhack_viewscreen +{ +public: + ViewscreenStocks() + { + selected_column = 0; + items_column.setTitle("Item"); + items_column.multiselect = false; + items_column.allow_search = true; + items_column.left_margin = 2; + + items_column.changeHighlight(0); + + populateItems(); + + items_column.selectDefaultEntry(); + } + + void feed(set *input) + { + bool key_processed = false; + switch (selected_column) + { + case 0: + key_processed = items_column.feed(input); + break; + } + + if (key_processed) + return; + + if (input->count(interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + } + + if (input->count(interface_key::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 (items_column.setHighlightByMouse()) + selected_column = 0; + + enabler->mouse_lbut = enabler->mouse_rbut = 0; + } + } + + void render() + { + if (Screen::isDismissed(this)) + return; + + dfhack_viewscreen::render(); + + Screen::clear(); + Screen::drawBorder(" Stocks "); + + items_column.display(selected_column == 0); + + int32_t y = gps->dimy - 3; + int32_t x = 2; + } + + std::string getFocusString() { return "stocks_view"; } + +private: + ListColumn items_column; + int selected_column; + + void populateItems() + { + items_column.clear(); + + df::item_flags bad_flags; + bad_flags.whole = 0; + bad_flags.bits.hostile = true; + bad_flags.bits.trader = true; + bad_flags.bits.in_building = true; + + std::vector &items = world->items.other[items_other_id::IN_PLAY]; + + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + + if (item->flags.whole & bad_flags.whole) + continue; + + df::item_type itype = item->getType(); + + items_column.add(Items::getDescription(item, 0, true), item); + } + + items_column.filterDisplay(); + } + + void validateColumn() + { + set_to_limit(selected_column, 0); + } + + void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + items_column.resize(); + } +}; + + +static command_result stocks_cmd(color_ostream &out, vector & parameters) +{ + if (!parameters.empty()) + { + if (toLower(parameters[0])[0] == 'v') + { + out << "Stocks plugin" << endl << "Version: " << PLUGIN_VERSION << endl; + return CR_OK; + } + else if (toLower(parameters[0])[0] == 's') + { + Screen::show(new ViewscreenStocks()); + return CR_OK; + } + } + + return CR_WRONG_USAGE; +} + + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (!gps) + out.printerr("Could not insert stocks plugin hooks!\n"); + + commands.push_back( + PluginCommand( + "stocks", "An improved stocks display screen", + stocks_cmd, false, "")); + + return CR_OK; +} + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + break; + default: + break; + } + + return CR_OK; +} From d0ba4f58811c03898740efdbd87607124704fc85 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 11 Apr 2013 20:52:46 +1200 Subject: [PATCH 63/73] Better Stocks screen - WIP --- plugins/stocks.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 93966c90a..7f3826231 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -26,6 +26,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } +#define MAX_NAME 30 #define SIDEBAR_WIDTH 30 static bool show_debugging = false; @@ -40,6 +41,64 @@ static void debug(const string &msg) } +/*struct FlagDisplay +{ + +};*/ + + +class StockListColumn : public ListColumn +{ + virtual void display_extras(const df::item *&item, int32_t &x, int32_t &y) const + { + if (item->flags.bits.in_job) + OutputString(COLOR_LIGHTBLUE, x, y, "J"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.rotten) + OutputString(COLOR_CYAN, x, y, "N"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.construction) + OutputString(COLOR_MAGENTA, x, y, "C"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.foreign) + OutputString(COLOR_BROWN, x, y, "G"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.owned) + OutputString(COLOR_GREEN, x, y, "O"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.forbid) + OutputString(COLOR_RED, x, y, "F"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.dump) + OutputString(COLOR_LIGHTMAGENTA, x, y, "D"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.on_fire) + OutputString(COLOR_LIGHTRED, x, y, "R"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.melt) + OutputString(COLOR_BLUE, x, y, "M"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + } +}; + + class ViewscreenStocks : public dfhack_viewscreen { public: @@ -116,7 +175,7 @@ public: std::string getFocusString() { return "stocks_view"; } private: - ListColumn items_column; + StockListColumn items_column; int selected_column; void populateItems() @@ -128,6 +187,7 @@ private: bad_flags.bits.hostile = true; bad_flags.bits.trader = true; bad_flags.bits.in_building = true; + bad_flags.bits.garbage_collect = true; std::vector &items = world->items.other[items_other_id::IN_PLAY]; @@ -139,8 +199,9 @@ private: continue; df::item_type itype = item->getType(); + auto label = pad_string(Items::getDescription(item, 0, true), MAX_NAME, false, true); - items_column.add(Items::getDescription(item, 0, true), item); + items_column.add(label, item); } items_column.filterDisplay(); From 17513283d4ba768d3bdfe7de69f8cf551f628e53 Mon Sep 17 00:00:00 2001 From: falconne Date: Sat, 13 Apr 2013 13:26:39 +1200 Subject: [PATCH 64/73] Better Stocks screen - WIP --- plugins/stocks.cpp | 180 +++++++++++++++++++++++++++++++++++++++++---- plugins/uicommon.h | 26 +++++-- 2 files changed, 186 insertions(+), 20 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 7f3826231..b6adf6827 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -9,12 +9,14 @@ #include "df/viewscreen_dwarfmodest.h" #include "df/items_other_id.h" #include "df/job.h" +#include "df/unit.h" #include "df/world.h" #include "modules/Gui.h" #include "modules/Items.h" #include "modules/Job.h" #include "modules/World.h" +#include "modules/Screen.h" using df::global::world; @@ -46,10 +48,10 @@ static void debug(const string &msg) };*/ - -class StockListColumn : public ListColumn +template +class StockListColumn : public ListColumn { - virtual void display_extras(const df::item *&item, int32_t &x, int32_t &y) const + virtual void display_extras(const T &item, int32_t &x, int32_t &y) const { if (item->flags.bits.in_job) OutputString(COLOR_LIGHTBLUE, x, y, "J"); @@ -61,11 +63,6 @@ class StockListColumn : public ListColumn else OutputString(COLOR_LIGHTBLUE, x, y, " "); - if (item->flags.bits.construction) - OutputString(COLOR_MAGENTA, x, y, "C"); - else - OutputString(COLOR_LIGHTBLUE, x, y, " "); - if (item->flags.bits.foreign) OutputString(COLOR_BROWN, x, y, "G"); else @@ -95,6 +92,11 @@ class StockListColumn : public ListColumn OutputString(COLOR_BLUE, x, y, "M"); else OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->flags.bits.in_inventory) + OutputString(COLOR_GREY, x, y, "I"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); } }; @@ -107,11 +109,16 @@ public: selected_column = 0; items_column.setTitle("Item"); items_column.multiselect = false; + items_column.auto_select = true; items_column.allow_search = true; items_column.left_margin = 2; + items_column.bottom_margin = 1; + items_column.search_margin = gps->dimx - SIDEBAR_WIDTH; items_column.changeHighlight(0); + hide_flags.whole = 0; + populateItems(); items_column.selectDefaultEntry(); @@ -137,7 +144,68 @@ public: return; } - if (input->count(interface_key::CURSOR_LEFT)) + if (input->count(interface_key::CUSTOM_SHIFT_G)) + { + hide_flags.bits.foreign = !hide_flags.bits.foreign; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_J)) + { + hide_flags.bits.in_job = !hide_flags.bits.in_job; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_N)) + { + hide_flags.bits.rotten = !hide_flags.bits.rotten; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_O)) + { + hide_flags.bits.owned = !hide_flags.bits.owned; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_F)) + { + hide_flags.bits.forbid = !hide_flags.bits.forbid; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_D)) + { + hide_flags.bits.dump = !hide_flags.bits.dump; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_R)) + { + hide_flags.bits.on_fire = !hide_flags.bits.on_fire; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_M)) + { + hide_flags.bits.melt = !hide_flags.bits.melt; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_I)) + { + hide_flags.bits.in_inventory = !hide_flags.bits.in_inventory; + populateItems(); + } + + else if (input->count(interface_key::CUSTOM_SHIFT_Z)) + { + input->clear(); + auto item = items_column.getFirstSelectedElem(); + if (!item) + return; + auto pos = getRealPos(item); + if (!pos) + return; + + Screen::dismiss(this); + send_key(interface_key::D_LOOK); + move_cursor(*pos); + } + + else if (input->count(interface_key::CURSOR_LEFT)) { --selected_column; validateColumn(); @@ -156,6 +224,20 @@ public: } } + 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 send_key(const df::interface_key &key) + { + set< df::interface_key > keys; + keys.insert(key); + Gui::getCurViewscreen(true)->feed(&keys); + } + void render() { if (Screen::isDismissed(this)) @@ -168,15 +250,73 @@ public: items_column.display(selected_column == 0); - int32_t y = gps->dimy - 3; - int32_t x = 2; + int32_t y = 1; + auto left_margin = gps->dimx - SIDEBAR_WIDTH; + int32_t x = left_margin - 2; + Screen::Pen border('\xDB', 8); + for (; y < gps->dimy - 1; y++) + { + paintTile(border, x, y); + } + + y = 2; + x = left_margin; + OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin); + OutputString(COLOR_LIGHTRED, x, y, "Press Shift-Hotkey to Toggle", true, left_margin); + OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE); + OutputFilterString(x, y, "Rotten", "N", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); + OutputFilterString(x, y, "Foreign Made", "G", !hide_flags.bits.foreign, true, left_margin, COLOR_BROWN); + OutputFilterString(x, y, "Owned", "O", !hide_flags.bits.owned, true, left_margin, COLOR_GREEN); + OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED); + OutputFilterString(x, y, "Dump", "D", !hide_flags.bits.dump, true, left_margin, COLOR_LIGHTMAGENTA); + OutputFilterString(x, y, "On Fire", "R", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); + OutputFilterString(x, y, "Melt", "M", !hide_flags.bits.melt, true, left_margin, COLOR_BLUE); + OutputFilterString(x, y, "In Inventory", "I", !hide_flags.bits.in_inventory, true, left_margin, COLOR_GREY); + + ++y; + OutputString(COLOR_BROWN, x, y, "Actions (" + int_to_string(items_column.getDisplayedListSize()) + " Items)", + true, left_margin); + OutputHotkeyString(x, y, "Zoom", "Shift-Z", true, left_margin); + } std::string getFocusString() { return "stocks_view"; } private: - StockListColumn items_column; + StockListColumn items_column; int selected_column; + df::item_flags hide_flags; + + df::coord *getRealPos(df::item *item) + { + if (item->flags.bits.in_inventory) + { + if (item->flags.bits.in_job) + { + auto ref = Items::getSpecificRef(item, specific_ref_type::JOB); + if (ref && ref->job) + { + if (ref->job->job_type == job_type::Eat || ref->job->job_type == job_type::Drink) + return nullptr; + + auto unit = Job::getWorker(ref->job); + if (unit) + return &unit->pos; + } + return nullptr; + } + else + { + auto unit = Items::getHolderUnit(item); + if (unit) + return &unit->pos; + + return nullptr; + } + } + + return &item->pos; + } void populateItems() { @@ -188,6 +328,12 @@ private: bad_flags.bits.trader = true; bad_flags.bits.in_building = true; bad_flags.bits.garbage_collect = true; + bad_flags.bits.spider_web = true; + bad_flags.bits.hostile = true; + bad_flags.bits.removed = true; + bad_flags.bits.dead_dwarf = true; + bad_flags.bits.murder = true; + bad_flags.bits.construction = true; std::vector &items = world->items.other[items_other_id::IN_PLAY]; @@ -195,10 +341,15 @@ private: { df::item *item = items[i]; - if (item->flags.whole & bad_flags.whole) + if (item->flags.whole & bad_flags.whole || item->flags.whole & hide_flags.whole) + continue; + + if (item->pos.x == -30000) + continue; + + if (!getRealPos(item)) continue; - df::item_type itype = item->getType(); auto label = pad_string(Items::getDescription(item, 0, true), MAX_NAME, false, true); items_column.add(label, item); @@ -216,6 +367,7 @@ private: { dfhack_viewscreen::resize(x, y); items_column.resize(); + items_column.search_margin = gps->dimx - SIDEBAR_WIDTH; } }; diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 891ec15a0..c54e8b0ae 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -71,12 +71,21 @@ void OutputString(UIColor color, int &x, int &y, const std::string &text, 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) +void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, + int left_margin = 0, int8_t text_color = COLOR_WHITE, int8_t hotkey_color = COLOR_LIGHTGREEN) { - OutputString(10, x, y, hotkey); + OutputString(hotkey_color, x, y, hotkey); string display(": "); display.append(text); - OutputString(color, x, y, display, newline, left_margin); + OutputString(text_color, x, y, display, newline, left_margin); +} + +void OutputFilterString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = false, + int left_margin = 0, int8_t hotkey_color = COLOR_LIGHTGREEN) +{ + OutputString(hotkey_color, x, y, hotkey); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((state) ? COLOR_WHITE : COLOR_GREY, x, y, text, 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) @@ -249,7 +258,7 @@ public: if (is_selected_column && allow_search) { - y = gps->dimy - bottom_margin; + y = gps->dimy - 3; int32_t x = search_margin; OutputHotkeyString(x, y, "Search" ,"S"); OutputString(COLOR_WHITE, x, y, ": "); @@ -265,13 +274,17 @@ public: search_string = toLower(search_string); for (size_t i = 0; i < list.size(); i++) { + ListEntry *entry = &list[i]; if (search_string.empty() || toLower(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; } + else if (auto_select) + { + entry->selected = false; + } } changeHighlight(0); feed_changed_highlight = true; @@ -459,7 +472,8 @@ public: { // Search query typing mode always on df::interface_key last_token = *input->rbegin(); - if (last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) + if ((last_token >= interface_key::STRING_A096 && last_token <= interface_key::STRING_A123) || + last_token == interface_key::STRING_A032) { // Standard character search_string += last_token - ascii_to_enum_offset; From d99b930f5835d4ac6736887ca6f5e4e16dd9dd74 Mon Sep 17 00:00:00 2001 From: falconne Date: Sun, 14 Apr 2013 12:23:47 +1200 Subject: [PATCH 65/73] Better Stocks - Quality & Wear filters --- plugins/stocks.cpp | 275 ++++++++++++++++++++++++++++++++++++++++----- plugins/uicommon.h | 5 + 2 files changed, 255 insertions(+), 25 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index b6adf6827..b9e864f57 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -11,12 +11,14 @@ #include "df/job.h" #include "df/unit.h" #include "df/world.h" +#include "df/item_quality.h" #include "modules/Gui.h" #include "modules/Items.h" #include "modules/Job.h" #include "modules/World.h" #include "modules/Screen.h" +#include "modules/Maps.h" using df::global::world; @@ -43,10 +45,13 @@ static void debug(const string &msg) } -/*struct FlagDisplay +static string getQualityName(const df::item_quality quality) { - -};*/ + if (gps->dimx - SIDEBAR_WIDTH < 60) + return int_to_string(quality); + else + return ENUM_KEY_STR(item_quality, quality); +} template class StockListColumn : public ListColumn @@ -97,6 +102,43 @@ class StockListColumn : public ListColumn OutputString(COLOR_GREY, x, y, "I"); else OutputString(COLOR_LIGHTBLUE, x, y, " "); + + if (item->isImproved()) + OutputString(COLOR_BLUE, x, y, "* "); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + + auto quality = static_cast(item->getQuality()); + if (quality > item_quality::Ordinary) + { + auto color = COLOR_BROWN; + switch(quality) + { + case item_quality::FinelyCrafted: + color = COLOR_CYAN; + break; + + case item_quality::Superior: + color = COLOR_LIGHTBLUE; + break; + + case item_quality::Exceptional: + color = COLOR_GREEN; + break; + + case item_quality::Masterful: + color = COLOR_LIGHTGREEN; + break; + + case item_quality::Artifact: + color = COLOR_BLUE; + break; + + default: + break; + } + OutputString(color, x, y, getQualityName(quality)); + } } }; @@ -104,6 +146,8 @@ class StockListColumn : public ListColumn class ViewscreenStocks : public dfhack_viewscreen { public: + static df::item_flags hide_flags; + ViewscreenStocks() { selected_column = 0; @@ -117,7 +161,23 @@ public: items_column.changeHighlight(0); - hide_flags.whole = 0; + apply_to_all = false; + hide_unflagged = false; + + checked_flags.bits.in_job = true; + checked_flags.bits.rotten = true; + checked_flags.bits.foreign = true; + checked_flags.bits.owned = true; + checked_flags.bits.forbid = true; + checked_flags.bits.dump = true; + checked_flags.bits.on_fire = true; + checked_flags.bits.melt = true; + checked_flags.bits.on_fire = true; + checked_flags.bits.in_inventory = true; + + min_quality = item_quality::Ordinary; + max_quality = item_quality::Artifact; + min_wear = 0; populateItems(); @@ -144,53 +204,100 @@ public: return; } - if (input->count(interface_key::CUSTOM_SHIFT_G)) + if (input->count(interface_key::CUSTOM_SHIFT_G)) { hide_flags.bits.foreign = !hide_flags.bits.foreign; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_J)) + else if (input->count(interface_key::CUSTOM_SHIFT_J)) { hide_flags.bits.in_job = !hide_flags.bits.in_job; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_N)) + else if (input->count(interface_key::CUSTOM_SHIFT_T)) { hide_flags.bits.rotten = !hide_flags.bits.rotten; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_O)) + else if (input->count(interface_key::CUSTOM_SHIFT_O)) { hide_flags.bits.owned = !hide_flags.bits.owned; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_F)) + else if (input->count(interface_key::CUSTOM_SHIFT_F)) { hide_flags.bits.forbid = !hide_flags.bits.forbid; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_D)) + else if (input->count(interface_key::CUSTOM_SHIFT_D)) { hide_flags.bits.dump = !hide_flags.bits.dump; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_R)) + else if (input->count(interface_key::CUSTOM_SHIFT_R)) { hide_flags.bits.on_fire = !hide_flags.bits.on_fire; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_M)) + else if (input->count(interface_key::CUSTOM_SHIFT_M)) { hide_flags.bits.melt = !hide_flags.bits.melt; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_I)) + else if (input->count(interface_key::CUSTOM_SHIFT_I)) { hide_flags.bits.in_inventory = !hide_flags.bits.in_inventory; populateItems(); } + else if (input->count(interface_key::CUSTOM_SHIFT_N)) + { + hide_unflagged = !hide_unflagged; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_C)) + { + setAllFlags(true); + populateItems(); + } + else if (input->count(interface_key::CUSTOM_SHIFT_E)) + { + setAllFlags(false); + populateItems(); + } + else if (input->count(interface_key::SECONDSCROLL_UP)) + { + if (min_quality > item_quality::Ordinary) + { + min_quality = static_cast(static_cast(min_quality) - 1); + populateItems(); + } + } + else if (input->count(interface_key::SECONDSCROLL_DOWN)) + { + if (min_quality < max_quality && min_quality < item_quality::Artifact) + { + min_quality = static_cast(static_cast(min_quality) + 1); + populateItems(); + } + } + else if (input->count(interface_key::SECONDSCROLL_PAGEUP)) + { + if (max_quality > min_quality && max_quality > item_quality::Ordinary) + { + max_quality = static_cast(static_cast(max_quality) - 1); + populateItems(); + } + } + else if (input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + if (max_quality < item_quality::Artifact) + { + max_quality = static_cast(static_cast(max_quality) + 1); + populateItems(); + } + } - else if (input->count(interface_key::CUSTOM_SHIFT_Z)) + else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { input->clear(); auto item = items_column.getFirstSelectedElem(); @@ -201,16 +308,34 @@ public: return; Screen::dismiss(this); + // Could be clever here, if item is in a container, to look inside the container. + // But that's different for built containers vs bags/pots in stockpiles. send_key(interface_key::D_LOOK); move_cursor(*pos); } + else if (input->count(interface_key::CUSTOM_SHIFT_A)) + { + apply_to_all = !apply_to_all; + } + else if (input->count(interface_key::CUSTOM_SHIFT_P)) + { + df::item_flags flags; + flags.bits.dump = true; + applyFlag(flags); + } + else if (input->count(interface_key::CUSTOM_SHIFT_B)) + { + df::item_flags flags; + flags.bits.forbid = true; + applyFlag(flags); + } - else if (input->count(interface_key::CURSOR_LEFT)) + else if (input->count(interface_key::CURSOR_LEFT)) { --selected_column; validateColumn(); } - else if (input->count(interface_key::CURSOR_RIGHT)) + else if (input->count(interface_key::CURSOR_RIGHT)) { selected_column++; validateColumn(); @@ -224,7 +349,7 @@ public: } } - void move_cursor(df::coord &pos) + void move_cursor(const df::coord &pos) { Gui::setCursorCoords(pos.x, pos.y, pos.z); send_key(interface_key::CURSOR_DOWN_Z); @@ -264,7 +389,7 @@ public: OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin); OutputString(COLOR_LIGHTRED, x, y, "Press Shift-Hotkey to Toggle", true, left_margin); OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE); - OutputFilterString(x, y, "Rotten", "N", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); + OutputFilterString(x, y, "Rotten", "T", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); OutputFilterString(x, y, "Foreign Made", "G", !hide_flags.bits.foreign, true, left_margin, COLOR_BROWN); OutputFilterString(x, y, "Owned", "O", !hide_flags.bits.owned, true, left_margin, COLOR_GREEN); OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED); @@ -272,12 +397,29 @@ public: OutputFilterString(x, y, "On Fire", "R", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); OutputFilterString(x, y, "Melt", "M", !hide_flags.bits.melt, true, left_margin, COLOR_BLUE); OutputFilterString(x, y, "In Inventory", "I", !hide_flags.bits.in_inventory, true, left_margin, COLOR_GREY); + OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY); + ++y; + OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin); + OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin); + ++y; + OutputHotkeyString(x, y, "Min Qual: ", "-+"); + OutputString(COLOR_BROWN, x, y, getQualityName(min_quality), true, left_margin); + OutputHotkeyString(x, y, "Max Qual: ", "/*"); + OutputString(COLOR_BROWN, x, y, getQualityName(max_quality), true, left_margin); + + OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); + OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin); + ++y; - OutputString(COLOR_BROWN, x, y, "Actions (" + int_to_string(items_column.getDisplayedListSize()) + " Items)", - true, left_margin); + OutputString(COLOR_BROWN, x, y, "Actions ("); + OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize())); + OutputString(COLOR_BROWN, x, y, " Items)", true, left_margin); OutputHotkeyString(x, y, "Zoom", "Shift-Z", true, left_margin); - + OutputHotkeyString(x, y, "Apply to: ", "Shift-A"); + OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin); + OutputHotkeyString(x, y, "Dump", "Shift-P", true, left_margin); + OutputHotkeyString(x, y, "Forbid", "Shift-B", true, left_margin); } std::string getFocusString() { return "stocks_view"; } @@ -285,9 +427,12 @@ public: private: StockListColumn items_column; int selected_column; - df::item_flags hide_flags; + bool apply_to_all, hide_unflagged; + df::item_flags checked_flags; + df::item_quality min_quality, max_quality; + int16_t min_wear; - df::coord *getRealPos(df::item *item) + static df::coord *getRealPos(df::item *item) { if (item->flags.bits.in_inventory) { @@ -318,6 +463,50 @@ private: return &item->pos; } + void applyFlag(const df::item_flags flags) + { + if (apply_to_all) + { + int state_to_apply = -1; + for (auto iter = items_column.getDisplayList().begin(); iter != items_column.getDisplayList().end(); iter++) + { + auto item = (*iter)->elem; + if (item) + { + // Set all flags based on state of first item in list + if (state_to_apply == -1) + state_to_apply = (item->flags.whole & flags.whole) ? 0 : 1; + + if (state_to_apply) + item->flags.whole |= flags.whole; + else + item->flags.whole &= ~flags.whole; + } + } + } + else + { + auto item = items_column.getFirstSelectedElem(); + if (item) + item->flags.whole ^= flags.whole; + } + } + + void setAllFlags(bool state) + { + hide_flags.bits.in_job = state; + hide_flags.bits.rotten = state; + hide_flags.bits.foreign = state; + hide_flags.bits.owned = state; + hide_flags.bits.forbid = state; + hide_flags.bits.dump = state; + hide_flags.bits.on_fire = state; + hide_flags.bits.melt = state; + hide_flags.bits.on_fire = state; + hide_flags.bits.in_inventory = state; + hide_unflagged = state; + } + void populateItems() { items_column.clear(); @@ -347,10 +536,41 @@ private: if (item->pos.x == -30000) continue; - if (!getRealPos(item)) + auto pos = getRealPos(item); + if (!pos) + continue; + + auto designation = Maps::getTileDesignation(*pos); + if (!designation) continue; - auto label = pad_string(Items::getDescription(item, 0, true), MAX_NAME, false, true); + if (designation->bits.hidden) + continue; // Items in parts of the map not yet revealed + + if (hide_unflagged && !(item->flags.whole & checked_flags.whole)) + continue; + + auto quality = static_cast(item->getQuality()); + if (quality < min_quality || quality > max_quality) + continue; + + auto wear = item->getWear(); + if (wear < min_wear) + continue; + + auto label = Items::getDescription(item, 0, false); + if (wear > 0) + { + string wearX = ""; + for (int i = 0; i < wear; i++) + { + wearX += "X"; + } + + label = wearX + label + wearX; + } + + label = pad_string(label, MAX_NAME, false, true); items_column.add(label, item); } @@ -371,6 +591,8 @@ private: } }; +df::item_flags ViewscreenStocks::hide_flags; + static command_result stocks_cmd(color_ostream &out, vector & parameters) { @@ -402,6 +624,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector *> &getDisplayList() + { + return display_list; + } + size_t getBaseListSize() { return list.size(); From 811bd191d5f28b25ef240ff759b5111d41f68fcb Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 14 Apr 2013 22:29:27 +1200 Subject: [PATCH 66/73] Add wear filter --- plugins/autotrade.cpp | 2 +- plugins/stocks.cpp | 57 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/plugins/autotrade.cpp b/plugins/autotrade.cpp index be9615ef0..62f7c05c0 100644 --- a/plugins/autotrade.cpp +++ b/plugins/autotrade.cpp @@ -25,7 +25,7 @@ using df::global::ui; using df::building_stockpilest; DFHACK_PLUGIN("autotrade"); -#define PLUGIN_VERSION 0.1 +#define PLUGIN_VERSION 0.2 /* diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index b9e864f57..7ac50fec8 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -12,6 +12,7 @@ #include "df/unit.h" #include "df/world.h" #include "df/item_quality.h" +#include "df/caravan_state.h" #include "modules/Gui.h" #include "modules/Items.h" @@ -53,6 +54,23 @@ static string getQualityName(const df::item_quality quality) return ENUM_KEY_STR(item_quality, quality); } +static bool can_trade() +{ + if (df::global::ui->caravans.size() == 0) + return false; + + for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++) + { + auto caravan = *it; + auto trade_state = caravan->trade_state; + auto time_remaining = caravan->time_remaining; + if ((trade_state != 1 && trade_state != 2) || time_remaining == 0) + return false; + } + + return true; +} + template class StockListColumn : public ListColumn { @@ -64,7 +82,7 @@ class StockListColumn : public ListColumn OutputString(COLOR_LIGHTBLUE, x, y, " "); if (item->flags.bits.rotten) - OutputString(COLOR_CYAN, x, y, "N"); + OutputString(COLOR_CYAN, x, y, "X"); else OutputString(COLOR_LIGHTBLUE, x, y, " "); @@ -214,7 +232,7 @@ public: hide_flags.bits.in_job = !hide_flags.bits.in_job; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_T)) + else if (input->count(interface_key::CUSTOM_SHIFT_X)) { hide_flags.bits.rotten = !hide_flags.bits.rotten; populateItems(); @@ -296,6 +314,14 @@ public: populateItems(); } } + else if (input->count(interface_key::CUSTOM_SHIFT_W)) + { + ++min_wear; + if (min_wear > 3) + min_wear = 0; + + populateItems(); + } else if (input->count(interface_key::CUSTOM_SHIFT_Z)) { @@ -389,7 +415,7 @@ public: OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin); OutputString(COLOR_LIGHTRED, x, y, "Press Shift-Hotkey to Toggle", true, left_margin); OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE); - OutputFilterString(x, y, "Rotten", "T", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); + OutputFilterString(x, y, "Rotten", "X", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); OutputFilterString(x, y, "Foreign Made", "G", !hide_flags.bits.foreign, true, left_margin, COLOR_BROWN); OutputFilterString(x, y, "Owned", "O", !hide_flags.bits.owned, true, left_margin, COLOR_GREEN); OutputFilterString(x, y, "Forbidden", "F", !hide_flags.bits.forbid, true, left_margin, COLOR_RED); @@ -407,10 +433,10 @@ public: OutputHotkeyString(x, y, "Max Qual: ", "/*"); OutputString(COLOR_BROWN, x, y, getQualityName(max_quality), true, left_margin); + ++y; OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); OutputString(COLOR_BROWN, x, y, int_to_string(min_wear), true, left_margin); - - + ++y; OutputString(COLOR_BROWN, x, y, "Actions ("); OutputString(COLOR_LIGHTGREEN, x, y, int_to_string(items_column.getDisplayedListSize())); @@ -561,10 +587,25 @@ private: auto label = Items::getDescription(item, 0, false); if (wear > 0) { - string wearX = ""; - for (int i = 0; i < wear; i++) + string wearX; + switch (wear) { - wearX += "X"; + case 1: + wearX = "x"; + break; + + case 2: + wearX = "X"; + break; + + case 3: + wearX = "xX"; + break; + + default: + wearX = "XX"; + break; + } label = wearX + label + wearX; From e70d7b404a986ec6ebdfd58f1c423cd7b589cb09 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 20 Apr 2013 12:09:07 +1200 Subject: [PATCH 67/73] Don't exclude webs, check for items in inventory before discarding those without a valid position. --- plugins/stocks.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 7ac50fec8..9299fd0d7 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -543,7 +543,6 @@ private: bad_flags.bits.trader = true; bad_flags.bits.in_building = true; bad_flags.bits.garbage_collect = true; - bad_flags.bits.spider_web = true; bad_flags.bits.hostile = true; bad_flags.bits.removed = true; bad_flags.bits.dead_dwarf = true; @@ -559,13 +558,13 @@ private: if (item->flags.whole & bad_flags.whole || item->flags.whole & hide_flags.whole) continue; - if (item->pos.x == -30000) - continue; - auto pos = getRealPos(item); if (!pos) continue; + if (pos->x == -30000) + continue; + auto designation = Maps::getTileDesignation(*pos); if (!designation) continue; From eb8b2faf66209e64d079b060642acdc76b54dcdf Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 20 Apr 2013 15:04:44 +1200 Subject: [PATCH 68/73] Stocks plugin: fix inventory check, add trade assignments, better hotkets. --- plugins/stocks.cpp | 365 ++++++++++++++++++++++++++++++++++++++++----- plugins/uicommon.h | 27 +++- 2 files changed, 354 insertions(+), 38 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 9299fd0d7..cebcb0eaa 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -5,6 +5,7 @@ // DF data structure definition headers #include "DataDefs.h" #include "Types.h" + #include "df/item.h" #include "df/viewscreen_dwarfmodest.h" #include "df/items_other_id.h" @@ -13,6 +14,8 @@ #include "df/world.h" #include "df/item_quality.h" #include "df/caravan_state.h" +#include "df/mandate.h" +#include "df/general_ref_building_holderst.h" #include "modules/Gui.h" #include "modules/Items.h" @@ -46,7 +49,11 @@ static void debug(const string &msg) } -static string getQualityName(const df::item_quality quality) +/* + * Utility + */ + +static string get_quality_name(const df::item_quality quality) { if (gps->dimx - SIDEBAR_WIDTH < 60) return int_to_string(quality); @@ -54,23 +61,236 @@ static string getQualityName(const df::item_quality quality) return ENUM_KEY_STR(item_quality, quality); } -static bool can_trade() + +/* + * Trade + */ + +static df::job *get_item_job(df::item *item) +{ + auto ref = Items::getSpecificRef(item, specific_ref_type::JOB); + if (ref && ref->job) + return ref->job; + + return nullptr; +} + +static df::item *get_container_of(df::item *item) +{ + auto container = Items::getContainer(item); + return (container) ? container : item; +} + +static bool is_marked_for_trade(df::item *item, df::item *container = nullptr) +{ + item = (container) ? container : get_container_of(item); + auto job = get_item_job(item); + if (!job) + return false; + + return job->job_type == job_type::BringItemToDepot; +} + +static bool check_mandates(df::item *item) +{ + for (auto it = world->mandates.begin(); it != world->mandates.end(); it++) + { + auto mandate = *it; + + if (mandate->mode != 0) + continue; + + if (item->getType() != mandate->item_type || + (mandate->item_subtype != -1 && item->getSubtype() != mandate->item_subtype)) + continue; + + if (mandate->mat_type != -1 && item->getMaterial() != mandate->mat_type) + continue; + + if (mandate->mat_index != -1 && item->getMaterialIndex() != mandate->mat_index) + continue; + + return false; + } + + return true; +} + +static bool can_trade_item(df::item *item) { - if (df::global::ui->caravans.size() == 0) + if (item->flags.bits.owned || item->flags.bits.artifact || item->flags.bits.spider_web || item->flags.bits.in_job) return false; - for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - auto caravan = *it; - auto trade_state = caravan->trade_state; - auto time_remaining = caravan->time_remaining; - if ((trade_state != 1 && trade_state != 2) || time_remaining == 0) + df::general_ref *ref = item->general_refs[i]; + + switch (ref->getType()) + { + case general_ref_type::UNIT_HOLDER: + return false; + + case general_ref_type::BUILDING_HOLDER: + return false; + + default: + break; + } + } + + for (size_t i = 0; i < item->specific_refs.size(); i++) + { + df::specific_ref *ref = item->specific_refs[i]; + + if (ref->type == specific_ref_type::JOB) + { + // Ignore any items assigned to a job + return false; + } + } + + return check_mandates(item); +} + +static bool can_trade_item_and_container(df::item *item) +{ + item = get_container_of(item); + + if (item->flags.bits.in_inventory) + return false; + + if (!can_trade_item(item)) + return false; + + vector contained_items; + Items::getContainedItems(item, &contained_items); + for (auto cit = contained_items.begin(); cit != contained_items.end(); cit++) + { + if (!can_trade_item(*cit)) return false; } return true; } +static bool is_in_inventory(df::item *item) +{ + item = get_container_of(item); + return item->flags.bits.in_inventory; +} + + +class TradeDepotInfo +{ +public: + TradeDepotInfo() + { + reset(); + } + + void prepareTradeVarables() + { + reset(); + for(auto bld_it = world->buildings.all.begin(); bld_it != world->buildings.all.end(); bld_it++) + { + auto bld = *bld_it; + if (!isUsableDepot(bld)) + continue; + + depot = bld; + id = depot->id; + trade_possible = caravansAvailable(); + break; + } + } + + bool assignItem(df::item *item) + { + item = get_container_of(item); + if (!can_trade_item_and_container(item)) + return false; + + auto href = df::allocate(); + if (!href) + return false; + + auto job = new df::job(); + + df::coord tpos(depot->centerx, depot->centery, depot->z); + job->pos = tpos; + + job->job_type = job_type::BringItemToDepot; + + // job <-> item link + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + return false; + } + + // job <-> building link + href->building_id = id; + depot->jobs.push_back(job); + job->general_refs.push_back(href); + + // add to job list + Job::linkIntoWorld(job); + + return true; + } + + void reset() + { + depot = 0; + trade_possible = false; + } + + bool canTrade() + { + return trade_possible; + } + +private: + int32_t id; + df::building *depot; + bool trade_possible; + + bool isUsableDepot(df::building* bld) + { + if (bld->getType() != building_type::TradeDepot) + return false; + + if (bld->getBuildStage() < bld->getMaxBuildStage()) + return false; + + if (bld->jobs.size() == 1 && bld->jobs[0]->job_type == job_type::DestroyBuilding) + return false; + + return true; + } + + bool caravansAvailable() + { + if (df::global::ui->caravans.size() == 0) + return false; + + for (auto it = df::global::ui->caravans.begin(); it != df::global::ui->caravans.end(); it++) + { + auto caravan = *it; + auto trade_state = caravan->trade_state; + auto time_remaining = caravan->time_remaining; + if ((trade_state != 1 && trade_state != 2) || time_remaining == 0) + return false; + } + + return true; + } +}; + +static TradeDepotInfo depot_info; + + template class StockListColumn : public ListColumn { @@ -116,11 +336,19 @@ class StockListColumn : public ListColumn else OutputString(COLOR_LIGHTBLUE, x, y, " "); - if (item->flags.bits.in_inventory) - OutputString(COLOR_GREY, x, y, "I"); + if (is_in_inventory(item)) + OutputString(COLOR_WHITE, x, y, "I"); else OutputString(COLOR_LIGHTBLUE, x, y, " "); + if (depot_info.canTrade()) + { + if (is_marked_for_trade(item)) + OutputString(COLOR_LIGHTGREEN, x, y, "T"); + else + OutputString(COLOR_LIGHTBLUE, x, y, " "); + } + if (item->isImproved()) OutputString(COLOR_BLUE, x, y, "* "); else @@ -155,16 +383,32 @@ class StockListColumn : public ListColumn default: break; } - OutputString(color, x, y, getQualityName(quality)); + OutputString(color, x, y, get_quality_name(quality)); } } }; +struct extra_filters +{ + bool hide_trade_marked, hide_in_inventory; + + extra_filters() + { + reset(); + } + + void reset() + { + hide_in_inventory = false; + hide_trade_marked = false; + } +}; class ViewscreenStocks : public dfhack_viewscreen { public: static df::item_flags hide_flags; + static extra_filters extra_hide_flags; ViewscreenStocks() { @@ -191,7 +435,6 @@ public: checked_flags.bits.on_fire = true; checked_flags.bits.melt = true; checked_flags.bits.on_fire = true; - checked_flags.bits.in_inventory = true; min_quality = item_quality::Ordinary; max_quality = item_quality::Artifact; @@ -202,6 +445,13 @@ public: items_column.selectDefaultEntry(); } + static void reset() + { + hide_flags.whole = 0; + extra_hide_flags.reset(); + depot_info.reset(); + } + void feed(set *input) { bool key_processed = false; @@ -222,52 +472,57 @@ public: return; } - if (input->count(interface_key::CUSTOM_SHIFT_G)) + if (input->count(interface_key::CUSTOM_CTRL_G)) { hide_flags.bits.foreign = !hide_flags.bits.foreign; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_J)) + else if (input->count(interface_key::CUSTOM_CTRL_J)) { hide_flags.bits.in_job = !hide_flags.bits.in_job; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_X)) + else if (input->count(interface_key::CUSTOM_CTRL_X)) { hide_flags.bits.rotten = !hide_flags.bits.rotten; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_O)) + else if (input->count(interface_key::CUSTOM_CTRL_O)) { hide_flags.bits.owned = !hide_flags.bits.owned; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_F)) + else if (input->count(interface_key::CUSTOM_CTRL_F)) { hide_flags.bits.forbid = !hide_flags.bits.forbid; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_D)) + else if (input->count(interface_key::CUSTOM_CTRL_D)) { hide_flags.bits.dump = !hide_flags.bits.dump; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_R)) + else if (input->count(interface_key::CUSTOM_CTRL_R)) { hide_flags.bits.on_fire = !hide_flags.bits.on_fire; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_M)) + else if (input->count(interface_key::CUSTOM_CTRL_M)) { hide_flags.bits.melt = !hide_flags.bits.melt; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_I)) + else if (input->count(interface_key::CUSTOM_CTRL_I)) { - hide_flags.bits.in_inventory = !hide_flags.bits.in_inventory; + extra_hide_flags.hide_in_inventory = !extra_hide_flags.hide_in_inventory; populateItems(); } - else if (input->count(interface_key::CUSTOM_SHIFT_N)) + else if (input->count(interface_key::CUSTOM_CTRL_T)) + { + extra_hide_flags.hide_trade_marked = !extra_hide_flags.hide_trade_marked; + populateItems(); + } + else if (input->count(interface_key::CUSTOM_CTRL_N)) { hide_unflagged = !hide_unflagged; populateItems(); @@ -343,18 +598,37 @@ public: { apply_to_all = !apply_to_all; } - else if (input->count(interface_key::CUSTOM_SHIFT_P)) + else if (input->count(interface_key::CUSTOM_SHIFT_D)) { df::item_flags flags; flags.bits.dump = true; applyFlag(flags); } - else if (input->count(interface_key::CUSTOM_SHIFT_B)) + else if (input->count(interface_key::CUSTOM_SHIFT_F)) { df::item_flags flags; flags.bits.forbid = true; applyFlag(flags); } + else if (input->count(interface_key::CUSTOM_SHIFT_T)) + { + if (apply_to_all) + { + auto &list = items_column.getDisplayList(); + for (auto iter = list.begin(); iter != list.end(); iter++) + { + auto item = (*iter)->elem; + if (item) + depot_info.assignItem(item); + } + } + else + { + auto item = items_column.getFirstSelectedElem(); + if (item) + depot_info.assignItem(item); + } + } else if (input->count(interface_key::CURSOR_LEFT)) { @@ -413,7 +687,7 @@ public: y = 2; x = left_margin; OutputString(COLOR_BROWN, x, y, "Filters", true, left_margin); - OutputString(COLOR_LIGHTRED, x, y, "Press Shift-Hotkey to Toggle", true, left_margin); + OutputString(COLOR_LIGHTRED, x, y, "Press Ctrl-Hotkey to Toggle", true, left_margin); OutputFilterString(x, y, "In Job", "J", !hide_flags.bits.in_job, true, left_margin, COLOR_LIGHTBLUE); OutputFilterString(x, y, "Rotten", "X", !hide_flags.bits.rotten, true, left_margin, COLOR_CYAN); OutputFilterString(x, y, "Foreign Made", "G", !hide_flags.bits.foreign, true, left_margin, COLOR_BROWN); @@ -422,16 +696,17 @@ public: OutputFilterString(x, y, "Dump", "D", !hide_flags.bits.dump, true, left_margin, COLOR_LIGHTMAGENTA); OutputFilterString(x, y, "On Fire", "R", !hide_flags.bits.on_fire, true, left_margin, COLOR_LIGHTRED); OutputFilterString(x, y, "Melt", "M", !hide_flags.bits.melt, true, left_margin, COLOR_BLUE); - OutputFilterString(x, y, "In Inventory", "I", !hide_flags.bits.in_inventory, true, left_margin, COLOR_GREY); + OutputFilterString(x, y, "In Inventory", "I", !extra_hide_flags.hide_in_inventory, true, left_margin, COLOR_WHITE); + OutputFilterString(x, y, "Trade", "T", !extra_hide_flags.hide_trade_marked, true, left_margin, COLOR_LIGHTGREEN); OutputFilterString(x, y, "No Flags", "N", !hide_unflagged, true, left_margin, COLOR_GREY); ++y; OutputHotkeyString(x, y, "Clear All", "Shift-C", true, left_margin); OutputHotkeyString(x, y, "Enable All", "Shift-E", true, left_margin); ++y; OutputHotkeyString(x, y, "Min Qual: ", "-+"); - OutputString(COLOR_BROWN, x, y, getQualityName(min_quality), true, left_margin); + OutputString(COLOR_BROWN, x, y, get_quality_name(min_quality), true, left_margin); OutputHotkeyString(x, y, "Max Qual: ", "/*"); - OutputString(COLOR_BROWN, x, y, getQualityName(max_quality), true, left_margin); + OutputString(COLOR_BROWN, x, y, get_quality_name(max_quality), true, left_margin); ++y; OutputHotkeyString(x, y, "Min Wear: ", "Shift-W"); @@ -444,8 +719,9 @@ public: OutputHotkeyString(x, y, "Zoom", "Shift-Z", true, left_margin); OutputHotkeyString(x, y, "Apply to: ", "Shift-A"); OutputString(COLOR_BROWN, x, y, (apply_to_all) ? "Listed" : "Selected", true, left_margin); - OutputHotkeyString(x, y, "Dump", "Shift-P", true, left_margin); - OutputHotkeyString(x, y, "Forbid", "Shift-B", true, left_margin); + OutputHotkeyString(x, y, "Dump", "Shift-D", true, left_margin); + OutputHotkeyString(x, y, "Forbid", "Shift-F", true, left_margin); + OutputHotkeyString(x, y, "Mark for Trade", "Shift-T", true, left_margin); } std::string getFocusString() { return "stocks_view"; } @@ -460,6 +736,7 @@ private: static df::coord *getRealPos(df::item *item) { + item = get_container_of(item); if (item->flags.bits.in_inventory) { if (item->flags.bits.in_job) @@ -529,8 +806,9 @@ private: hide_flags.bits.on_fire = state; hide_flags.bits.melt = state; hide_flags.bits.on_fire = state; - hide_flags.bits.in_inventory = state; hide_unflagged = state; + extra_hide_flags.hide_trade_marked = state; + extra_hide_flags.hide_in_inventory = state; } void populateItems() @@ -549,6 +827,8 @@ private: bad_flags.bits.murder = true; bad_flags.bits.construction = true; + depot_info.prepareTradeVarables(); + std::vector &items = world->items.other[items_other_id::IN_PLAY]; for (size_t i = 0; i < items.size(); i++) @@ -558,6 +838,10 @@ private: if (item->flags.whole & bad_flags.whole || item->flags.whole & hide_flags.whole) continue; + auto container = get_container_of(item); + if (container->flags.whole & bad_flags.whole) + continue; + auto pos = getRealPos(item); if (!pos) continue; @@ -572,9 +856,19 @@ private: if (designation->bits.hidden) continue; // Items in parts of the map not yet revealed - if (hide_unflagged && !(item->flags.whole & checked_flags.whole)) + bool trade_marked = is_marked_for_trade(item, container); + if (extra_hide_flags.hide_trade_marked && trade_marked) + continue; + + if (extra_hide_flags.hide_in_inventory && container->flags.bits.in_inventory) continue; + if (hide_unflagged && (!(item->flags.whole & checked_flags.whole) && + !trade_marked && !container->flags.bits.in_inventory)) + { + continue; + } + auto quality = static_cast(item->getQuality()); if (quality < min_quality || quality > max_quality) continue; @@ -632,6 +926,7 @@ private: }; df::item_flags ViewscreenStocks::hide_flags; +extra_filters ViewscreenStocks::extra_hide_flags; static command_result stocks_cmd(color_ostream &out, vector & parameters) @@ -664,7 +959,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL; display_list.clear(); + search_string = toLower(search_string); + vector search_tokens; + if (!search_string.empty()) + split_string(&search_tokens, search_string, " "); + for (size_t i = 0; i < list.size(); i++) { ListEntry *entry = &list[i]; - if (search_string.empty() || toLower(list[i].text).find(search_string) != string::npos) + + bool include_item = true; + if (!search_string.empty()) + { + string item_string = toLower(list[i].text); + for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) + { + if (!si->empty() && item_string.find(*si) == string::npos) + { + include_item = false; + break; + } + } + } + + if (include_item) { display_list.push_back(entry); if (entry == prev_selected) From 80de3c05fa15dca96d084808fca02df0cc3da850 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Wed, 24 Apr 2013 23:01:35 +1200 Subject: [PATCH 69/73] Stocks plugin: Add keywords to search for flags. --- plugins/stocks.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++---- plugins/uicommon.h | 3 ++- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index cebcb0eaa..10594fe9e 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -27,7 +27,7 @@ using df::global::world; DFHACK_PLUGIN("stocks"); -#define PLUGIN_VERSION 0.1 +#define PLUGIN_VERSION 0.2 DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { @@ -291,6 +291,46 @@ private: static TradeDepotInfo depot_info; +static string get_keywords(df::item *item) +{ + string keywords; + + if (item->flags.bits.in_job) + keywords += "job "; + + if (item->flags.bits.rotten) + keywords += "rotten "; + + if (item->flags.bits.foreign) + keywords += "foreign "; + + if (item->flags.bits.owned) + keywords += "owned "; + + if (item->flags.bits.forbid) + keywords += "forbid "; + + if (item->flags.bits.dump) + keywords += "dump "; + + if (item->flags.bits.on_fire) + keywords += "fire "; + + if (item->flags.bits.melt) + keywords += "melt "; + + if (is_in_inventory(item)) + keywords += "inventory "; + + if (depot_info.canTrade()) + { + if (is_marked_for_trade(item)) + keywords += "trade "; + } + + return keywords; +} + template class StockListColumn : public ListColumn { @@ -603,12 +643,14 @@ public: df::item_flags flags; flags.bits.dump = true; applyFlag(flags); + populateItems(); } else if (input->count(interface_key::CUSTOM_SHIFT_F)) { df::item_flags flags; flags.bits.forbid = true; applyFlag(flags); + populateItems(); } else if (input->count(interface_key::CUSTOM_SHIFT_T)) { @@ -621,12 +663,14 @@ public: if (item) depot_info.assignItem(item); } + + populateItems(); } else { auto item = items_column.getFirstSelectedElem(); - if (item) - depot_info.assignItem(item); + if (item && depot_info.assignItem(item)) + populateItems(); } } @@ -906,7 +950,8 @@ private: label = pad_string(label, MAX_NAME, false, true); - items_column.add(label, item); + auto entry = ListEntry(label, item, get_keywords(item)); + items_column.add(entry); } items_column.filterDisplay(); diff --git a/plugins/uicommon.h b/plugins/uicommon.h index fa58c7f90..147556ff1 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -288,7 +288,8 @@ public: string item_string = toLower(list[i].text); for (auto si = search_tokens.begin(); si != search_tokens.end(); si++) { - if (!si->empty() && item_string.find(*si) == string::npos) + if (!si->empty() && item_string.find(*si) == string::npos && + list[i].keywords.find(*si) == string::npos) { include_item = false; break; From fd265b37d6241ed1c5f2d2d0dab56e4b546fd362 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Fri, 29 Mar 2013 01:48:45 +0100 Subject: [PATCH 70/73] Added lua GUI front-end for autobutcher. --- plugins/CMakeLists.txt | 2 +- plugins/lua/zone.lua | 12 + plugins/zone.cpp | 311 +++++++++++++++++++++- scripts/gui/autobutcher.lua | 496 ++++++++++++++++++++++++++++++++++++ 4 files changed, 819 insertions(+), 2 deletions(-) create mode 100644 plugins/lua/zone.lua create mode 100644 scripts/gui/autobutcher.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c21e50434..3d38616ea 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -112,7 +112,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(tweak tweak.cpp) DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(lair lair.cpp) - DFHACK_PLUGIN(zone zone.cpp) + DFHACK_PLUGIN(zone zone.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(catsplosion catsplosion.cpp) DFHACK_PLUGIN(regrass regrass.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) diff --git a/plugins/lua/zone.lua b/plugins/lua/zone.lua new file mode 100644 index 000000000..f6a136690 --- /dev/null +++ b/plugins/lua/zone.lua @@ -0,0 +1,12 @@ +local _ENV = mkmodule('plugins.zone') + +--[[ + + Native functions: + + * autobutcher_isEnabled() + * autowatch_isEnabled() + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 3195cffda..8907e507b 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -40,6 +40,11 @@ using namespace std; #include "Console.h" #include "Export.h" #include "PluginManager.h" +#include "MiscUtils.h" + +#include "LuaTools.h" +#include "DataFuncs.h" + #include "modules/Units.h" #include "modules/Maps.h" #include "modules/Gui.h" @@ -51,7 +56,7 @@ using namespace std; #include "MiscUtils.h" #include -#include +#include "df/ui.h" #include "df/world.h" #include "df/world_raws.h" #include "df/building_def.h" @@ -2716,6 +2721,8 @@ bool compareUnitAgesOlder(df::unit* i, df::unit* j) return (age_i > age_j); } + + //enum WatchedRaceSubtypes //{ // femaleKid=0, @@ -2892,6 +2899,17 @@ public: // to ignore them for a while but still keep the target count settings std::vector watched_races; +// helper for sorting the watchlist alphabetically +bool compareRaceNames(WatchedRace* i, WatchedRace* j) +{ + string name_i = getRaceName(i->raceId); + string name_j = getRaceName(j->raceId); + + return (name_i < name_j); +} + +static void autobutcher_sortWatchList(color_ostream &out); + // default target values for autobutcher static int default_fk = 5; static int default_mk = 1; @@ -3282,6 +3300,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) WatchedRace * w = new WatchedRace(watch_race, target_raceids.back(), target_fk, target_mk, target_fa, target_ma); w->UpdateConfig(out); watched_races.push_back(w); + autobutcher_sortWatchList(out); } target_raceids.pop_back(); } @@ -3367,6 +3386,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) announce = "New race added to autobutcher watchlist: " + getRaceName(w->raceId); Gui::showAnnouncement(announce, 2, false); //out << announce << endl; + autobutcher_sortWatchList(out); } } @@ -3475,6 +3495,7 @@ command_result init_autobutcher(color_ostream &out) w->rconfig = *p; watched_races.push_back(w); } + autobutcher_sortWatchList(out); return CR_OK; } @@ -3541,6 +3562,294 @@ command_result cleanup_autonestbox(color_ostream &out) return CR_OK; } +///////////////////////////////////// +// API functions to control autobutcher with a lua script + +static bool autobutcher_isEnabled() { return enable_autobutcher; } +static bool autowatch_isEnabled() { return enable_autobutcher_autowatch; } + +static size_t autobutcher_getSleep(color_ostream &out) +{ + return sleep_autobutcher; +} + +static void autobutcher_setSleep(color_ostream &out, size_t ticks) +{ + sleep_autobutcher = ticks; + if(config_autobutcher.isValid()) + config_autobutcher.ival(1) = sleep_autobutcher; +} + +static void autobutcher_setEnabled(color_ostream &out, bool enable) +{ + if(enable) + { + enable_autobutcher = true; + start_autobutcher(out); + autoButcher(out, false); + } + else + { + enable_autobutcher = false; + if(config_autobutcher.isValid()) + config_autobutcher.ival(0) = enable_autobutcher; + out << "Autobutcher stopped." << endl; + } +} + +static void autowatch_setEnabled(color_ostream &out, bool enable) +{ + if(enable) + { + out << "Auto-adding to watchlist started." << endl; + enable_autobutcher_autowatch = true; + if(config_autobutcher.isValid()) + config_autobutcher.ival(2) = enable_autobutcher_autowatch; + } + else + { + out << "Auto-adding to watchlist stopped." << endl; + enable_autobutcher_autowatch = false; + if(config_autobutcher.isValid()) + config_autobutcher.ival(2) = enable_autobutcher_autowatch; + } +} + +static size_t autobutcher_getWatchListSize() +{ + return watched_races.size(); +} + +// get race name for a watchlist index +static std::string autobutcher_getWatchListRace(color_ostream &out, size_t idx) +{ + if(idx >= watched_races.size()) + return "INVALID"; + + WatchedRace * w = watched_races[idx]; + return getRaceName(w->raceId); +} + +// get FK for a watchlist index +static size_t autobutcher_getWatchListRaceFK(color_ostream &out, size_t idx) +{ + if(idx >= watched_races.size()) + return -1; + + WatchedRace * w = watched_races[idx]; + return w->fk; +} + +// get FA for a watchlist index +static size_t autobutcher_getWatchListRaceFA(color_ostream &out, size_t idx) +{ + if(idx >= watched_races.size()) + return -1; + + WatchedRace * w = watched_races[idx]; + return w->fa; +} + +// get MK for a watchlist index +static size_t autobutcher_getWatchListRaceMK(color_ostream &out, size_t idx) +{ + if(idx >= watched_races.size()) + return -1; + + WatchedRace * w = watched_races[idx]; + return w->mk; +} + +// get MA for a watchlist index +static size_t autobutcher_getWatchListRaceMA(color_ostream &out, size_t idx) +{ + if(idx >= watched_races.size()) + return -1; + + WatchedRace * w = watched_races[idx]; + return w->ma; +} + +// set FK for a watchlist index +static void autobutcher_setWatchListRaceFK(color_ostream &out, size_t idx, size_t value) +{ + if(idx >= watched_races.size()) + return; + + WatchedRace * w = watched_races[idx]; + w->fk = value; + w->UpdateConfig(out); +} + +// set FA for a watchlist index +static void autobutcher_setWatchListRaceFA(color_ostream &out, size_t idx, size_t value) +{ + if(idx >= watched_races.size()) + return; + + WatchedRace * w = watched_races[idx]; + w->fa = value; + w->UpdateConfig(out); +} + +// set MK for a watchlist index +static void autobutcher_setWatchListRaceMK(color_ostream &out, size_t idx, size_t value) +{ + if(idx >= watched_races.size()) + return; + + WatchedRace * w = watched_races[idx]; + w->mk = value; + w->UpdateConfig(out); +} + +// set MA for a watchlist index +static void autobutcher_setWatchListRaceMA(color_ostream &out, size_t idx, size_t value) +{ + if(idx >= watched_races.size()) + return; + + WatchedRace * w = watched_races[idx]; + w->ma = value; + w->UpdateConfig(out); +} + +// check if "watch" is enabled for watchlist index +static bool autobutcher_isWatchListRaceWatched(color_ostream &out, size_t idx) +{ + if(idx >= watched_races.size()) + return false; + + WatchedRace * w = watched_races[idx]; + return w->isWatched; +} + +// set "watched" status for a watchlist index +static void autobutcher_setWatchListRaceWatched(color_ostream &out, size_t idx, bool watched) +{ + if(idx >= watched_races.size()) + return; + + WatchedRace * w = watched_races[idx]; + w->isWatched = watched; + w->UpdateConfig(out); +} + +// remove entry from watchlist +static void autobutcher_removeFromWatchList(color_ostream &out, size_t idx) +{ + if(idx >= watched_races.size()) + return; + + WatchedRace * w = watched_races[idx]; + w->RemoveConfig(out); + watched_races.erase(watched_races.begin()+idx); +} + +// sort watchlist alphabetically +static void autobutcher_sortWatchList(color_ostream &out) +{ + sort(watched_races.begin(), watched_races.end(), compareRaceNames); +} + +// get default target values for new races +static size_t autobutcher_getDefaultMK(color_ostream &out) +{ + return default_mk; +} +static size_t autobutcher_getDefaultMA(color_ostream &out) +{ + return default_ma; +} +static size_t autobutcher_getDefaultFK(color_ostream &out) +{ + return default_fk; +} +static size_t autobutcher_getDefaultFA(color_ostream &out) +{ + return default_fa; +} + +// set default target values for new races +static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma) +{ + default_fk = fk; + default_mk = mk; + default_fa = fa; + default_ma = ma; + if(config_autobutcher.isValid()) + { + config_autobutcher.ival(3) = default_fk; + config_autobutcher.ival(4) = default_mk; + config_autobutcher.ival(5) = default_fa; + config_autobutcher.ival(6) = default_ma; + } +} + +// set default target values for ALL races (update watchlist and set new default) +static void autobutcher_setDefaultTargetAll(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma) +{ + for(size_t i=0; ifk = fk; + w->mk = mk; + w->fa = fa; + w->ma = ma; + w->UpdateConfig(out); + } + autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); +} + +static std::string testString(color_ostream &out, size_t i) +{ + out << "where will this be written?" << std::endl; + return getRaceName(i); +} + +// DFHACK_LUA_FUNCTION(autobutcher_setEnabled), + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(autobutcher_isEnabled), + DFHACK_LUA_FUNCTION(autowatch_isEnabled), + DFHACK_LUA_FUNCTION(autobutcher_setEnabled), + DFHACK_LUA_FUNCTION(autowatch_setEnabled), + DFHACK_LUA_FUNCTION(autobutcher_getSleep), + DFHACK_LUA_FUNCTION(autobutcher_setSleep), + DFHACK_LUA_FUNCTION(autobutcher_getWatchListSize), + DFHACK_LUA_FUNCTION(autobutcher_getWatchListRace), + + DFHACK_LUA_FUNCTION(autobutcher_isWatchListRaceWatched), + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceWatched), + + DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceFK), + DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceFA), + DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceMK), + DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceMA), + + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceFK), + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceFA), + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceMK), + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceMA), + + DFHACK_LUA_FUNCTION(autobutcher_getDefaultFK), + DFHACK_LUA_FUNCTION(autobutcher_getDefaultFA), + DFHACK_LUA_FUNCTION(autobutcher_getDefaultMK), + DFHACK_LUA_FUNCTION(autobutcher_getDefaultMA), + + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), + + DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), + DFHACK_LUA_FUNCTION(autobutcher_sortWatchList), + + DFHACK_LUA_FUNCTION(testString), + DFHACK_LUA_END +}; + +// end lua API + + //START zone filters using df::global::ui_building_item_cursor; diff --git a/scripts/gui/autobutcher.lua b/scripts/gui/autobutcher.lua new file mode 100644 index 000000000..1c62775ba --- /dev/null +++ b/scripts/gui/autobutcher.lua @@ -0,0 +1,496 @@ +-- A GUI front-end for the autobutcher plugin. +-- requires to be called from the stock screen (z) + + +--[[ + + API overview (zone/autobutcher plugin functions which can be used by this lua script): + + autobutcher_isEnabled() - returns true if autobutcher is running + autowatch_isEnabled() - returns true if autowatch is running + autobutcher_getSleep() - get sleep timer in ticks + autobutcher_setSleep(int) - set sleep timer in ticks + autobutcher_getWatchListSize() - return size of watchlist + autobutcher_getWatchListRace(idx) - return race name for this watchlist index + + autobutcher_isWatchListRaceWatched(idx) - true if this watchlist index is watched + autobutcher_setWatchListRaceWatched(idx, bool) - set watchlist index to watched/unwatched + + autobutcher_getWatchListRaceFK() - get target fk + autobutcher_getWatchListRaceFA() - get target fa + autobutcher_getWatchListRaceMK() - get target mk + autobutcher_getWatchListRaceMA() - get target ma + + autobutcher_setWatchListRaceFK(value) - set fk + autobutcher_setWatchListRaceFA(value) - set fa + autobutcher_setWatchListRaceMK(value) - set mk + autobutcher_setWatchListRaceMA(value) - set ma + + autobutcher_removeFromWatchList(idx) - remove watchlist entry + autobutcher_sortWatchList() - sort the watchlist alphabetically + + testString(id) - returns race name for this race id (gonna be removed soon) + +--]] + + +local gui = require 'gui' +local utils = require 'utils' +local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' + +local plugin = require 'plugins.zone' + +WatchList = defclass(WatchList, gui.FramedScreen) + +WatchList.ATTRS { + frame_title = 'Autobutcher Watchlist', + frame_inset = 0, -- cover full DF window + frame_background = COLOR_BLACK, + frame_style = gui.BOUNDARY_FRAME, +} + +-- width of the race name column in the UI +local racewidth = 25 + +function nextAutowatchState() + if(plugin.autowatch_isEnabled()) then + return 'stop' + end + return 'start' +end + +function nextAutobutcherState() + if(plugin.autobutcher_isEnabled()) then + return 'stop' + end + return 'start' +end + +function getSleepTimer() + return plugin.autobutcher_getSleep() +end + +function setSleepTimer(ticks) + plugin.autobutcher_setSleep(ticks) +end + +function WatchList:init(args) + self:addviews{ + widgets.Panel{ + frame = { l = 0, r = 0 }, + frame_inset = 1, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + text_pen = COLOR_CYAN, + text = { + { text = 'Race', width = racewidth }, ' ', + { text = 'female', width = 6 }, ' ', + { text = ' male', width = 6 }, ' ', + { text = 'Female', width = 6 }, ' ', + { text = ' Male', width = 6 }, ' ', + { text = 'watching?' }, + NEWLINE, + { text = '', width = racewidth }, ' ', + { text = ' kids', width = 6 }, ' ', + { text = ' kids', width = 6 }, ' ', + { text = 'adults', width = 6 }, ' ', + { text = 'adults', width = 6 }, ' ', + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 3, b = 3 }, + not_found_label = 'Watchlist is empty.', + edit_pen = COLOR_LIGHTCYAN, + text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, + cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, + --on_select = self:callback('onSelectConstraint'), + }, + widgets.Label{ + view_id = 'bottom_ui', + frame = { b = 0, h = 1 }, + text = 'filled by updateBottom()' + } + } + }, + } + + self:initListChoices() + self:updateBottom() +end + +-- update the bottom part of the UI (after sleep timer changed etc) +function WatchList:updateBottom() + self.subviews.bottom_ui:setText( + { + { key = 'CUSTOM_F', text = ': f kids', + on_activate = self:callback('onEditFK') }, ', ', + { key = 'CUSTOM_M', text = ': m kids', + on_activate = self:callback('onEditMK') }, ', ', + { key = 'CUSTOM_SHIFT_F', text = ': f adults', + on_activate = self:callback('onEditFA') }, ', ', + { key = 'CUSTOM_SHIFT_M', text = ': m adults', + on_activate = self:callback('onEditMA') }, ', ', + { key = 'CUSTOM_SHIFT_X', text = ': Delete', + on_activate = self:callback('onDeleteConstraint') }, ', ', + { key = 'CUSTOM_W', text = ': toggle watch', + on_activate = self:callback('onToggleWatching') }, ', ', NEWLINE, + { key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher', + on_activate = self:callback('onToggleAutobutcher') }, ', ', + { key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch', + on_activate = self:callback('onToggleAutowatch') }, ', ', + { key = 'CUSTOM_SHIFT_S', text = ': sleep timer ('..getSleepTimer()..' ticks)', + on_activate = self:callback('onEditSleepTimer') }, ', ', + }) +end + +function WatchList:initListChoices() + + local choices = {} + + -- first two rows are for "edit all races" and "edit new races" + local fk = plugin.autobutcher_getDefaultFK() + local fa = plugin.autobutcher_getDefaultFA() + local mk = plugin.autobutcher_getDefaultMK() + local ma = plugin.autobutcher_getDefaultMA() + local watched = '---' + + table.insert (choices, { + text = { + { text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ', + { text = tostring(fk), width = 7, rjustify = true }, + { text = tostring(mk), width = 7, rjustify = true }, + { text = tostring(fa), width = 7, rjustify = true }, + { text = tostring(ma), width = 7, rjustify = true }, + { text = watched, width = 6, rjustify = true } + } + }) + + table.insert (choices, { + text = { + { text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ', + { text = tostring(fk), width = 7, rjustify = true }, + { text = tostring(mk), width = 7, rjustify = true }, + { text = tostring(fa), width = 7, rjustify = true }, + { text = tostring(ma), width = 7, rjustify = true }, + { text = watched, width = 6, rjustify = true } + } + }) + + -- fill with watchlist + for i=0, plugin.autobutcher_getWatchListSize()-1 do + local racestr = plugin.autobutcher_getWatchListRace(i) + fk = plugin.autobutcher_getWatchListRaceFK(i) + fa = plugin.autobutcher_getWatchListRaceFA(i) + mk = plugin.autobutcher_getWatchListRaceMK(i) + ma = plugin.autobutcher_getWatchListRaceMA(i) + local watched = 'no' + if plugin.autobutcher_isWatchListRaceWatched(i) then + watched = 'yes' + end + + table.insert (choices, { + text = { + { text = racestr, width = racewidth, pad_char = ' ' }, --' ', + { text = tostring(fk), width = 7, rjustify = true }, + { text = tostring(mk), width = 7, rjustify = true }, + { text = tostring(fa), width = 7, rjustify = true }, + { text = tostring(ma), width = 7, rjustify = true }, + { text = watched, width = 6, rjustify = true } + } + }) + end + + local list = self.subviews.list + list:setChoices(choices) +end + +function WatchList:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + else + WatchList.super.onInput(self, keys) + end +end + +-- check the user input for target population values +function WatchList:checkUserInput(count, text) + if count == nil then + dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) + return false + end + if count < 0 then + dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED) + return false + end + return true +end + +-- check the user input for sleep timer +function WatchList:checkUserInputSleep(count, text) + if count == nil then + dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) + return false + end + if count < 1000 then + dlg.showMessage('Invalid Number', + 'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!', + COLOR_LIGHTRED) + return false + end + return true +end + +function WatchList:onEditFK() + local selidx,selobj = self.subviews.list:getSelected() + local fk = plugin.autobutcher_getDefaultFK(); + local mk = plugin.autobutcher_getDefaultMK(); + local fa = plugin.autobutcher_getDefaultFA(); + local ma = plugin.autobutcher_getDefaultMA(); + local race = 'ALL RACES PLUS NEW' + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + local watchindex = selidx - 3 + if selidx > 2 then + fk = plugin.autobutcher_getWatchListRaceFK(watchindex) + race = plugin.autobutcher_getWatchListRace(watchindex) + end + + dlg.showInputPrompt( + 'Race: '..race, + 'Enter desired maximum of female kids:', + COLOR_WHITE, + ' '..fk, + function(text) + local count = tonumber(text) + if self:checkUserInput(count, text) then + fk = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRaceFK(watchindex, fk) + end + self:initListChoices() + end + end + ) +end + +function WatchList:onEditMK() + local selidx = self.subviews.list:getSelected() + local fk = plugin.autobutcher_getDefaultFK(); + local mk = plugin.autobutcher_getDefaultMK(); + local fa = plugin.autobutcher_getDefaultFA(); + local ma = plugin.autobutcher_getDefaultMA(); + local race = 'ALL RACES PLUS NEW' + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + local watchindex = selidx - 3 + if selidx > 2 then + mk = plugin.autobutcher_getWatchListRaceMK(watchindex) + race = plugin.autobutcher_getWatchListRace(watchindex) + end + + dlg.showInputPrompt( + 'Race: '..race, + 'Enter desired maximum of male kids:', + COLOR_WHITE, + ' '..mk, + function(text) + local count = tonumber(text) + if self:checkUserInput(count, text) then + mk = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRaceMK(watchindex, mk) + end + self:initListChoices() + end + end + ) +end + +function WatchList:onEditFA() + local selidx = self.subviews.list:getSelected() + local fk = plugin.autobutcher_getDefaultFK(); + local mk = plugin.autobutcher_getDefaultMK(); + local fa = plugin.autobutcher_getDefaultFA(); + local ma = plugin.autobutcher_getDefaultMA(); + local race = 'ALL RACES PLUS NEW' + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + local watchindex = selidx - 3 + if selidx > 2 then + fa = plugin.autobutcher_getWatchListRaceFA(watchindex) + race = plugin.autobutcher_getWatchListRace(watchindex) + end + + dlg.showInputPrompt( + 'Race: '..race, + 'Enter desired maximum of female adults:', + COLOR_WHITE, + ' '..fa, + function(text) + local count = tonumber(text) + if self:checkUserInput(count, text) then + fa = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRaceFA(watchindex, fa) + end + self:initListChoices() + end + end + ) +end + +function WatchList:onEditMA() + local selidx = self.subviews.list:getSelected() + local fk = plugin.autobutcher_getDefaultFK(); + local mk = plugin.autobutcher_getDefaultMK(); + local fa = plugin.autobutcher_getDefaultFA(); + local ma = plugin.autobutcher_getDefaultMA(); + local race = 'ALL RACES PLUS NEW' + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + local watchindex = selidx - 3 + if selidx > 2 then + ma = plugin.autobutcher_getWatchListRaceMA(watchindex) + race = plugin.autobutcher_getWatchListRace(watchindex) + end + + dlg.showInputPrompt( + 'Race: '..race, + 'Enter desired maximum of male adults:', + COLOR_WHITE, + ' '..ma, + function(text) + local count = tonumber(text) + if self:checkUserInput(count, text) then + ma = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRaceMA(watchindex, ma) + end + self:initListChoices() + end + end + ) +end + +function WatchList:onEditSleepTimer() + local sleep = getSleepTimer() + dlg.showInputPrompt( + 'Edit Sleep Timer', + 'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)', + COLOR_WHITE, + ' '..sleep, + function(text) + local count = tonumber(text) + if self:checkUserInputSleep(count, text) then + sleep = count + setSleepTimer(sleep) + self:updateBottom() + end + end + ) +end + +function WatchList:onToggleWatching() + local selidx = self.subviews.list:getSelected() + if(selidx == 0) then + --print('special handling for zero - list empty?') + end + if (selidx == 1) then + --print('special handling for first row - ALL animals') + end + if (selidx == 2) then + --print('special handling for second row - NEW animals') + end + if selidx > 2 then + --print('handling for single animal on watchlist') + local idx = selidx - 3 + if plugin.autobutcher_isWatchListRaceWatched(idx) then + plugin.autobutcher_setWatchListRaceWatched(idx, false) + else + plugin.autobutcher_setWatchListRaceWatched(idx, true) + end + end + self:initListChoices() +end + +function WatchList:onDeleteConstraint() + local selidx,selobj = self.subviews.list:getSelected() + if(selidx < 3) then + -- print('cannot delete this entry') + return + end + local idx = selidx - 3 + dlg.showYesNoPrompt( + 'Delete from Watchlist', + 'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)', + COLOR_YELLOW, + function() + plugin.autobutcher_removeFromWatchList(idx) + self:initListChoices() + end + ) +end + +function WatchList:onToggleAutobutcher() + if(plugin.autobutcher_isEnabled()) then + plugin.autobutcher_setEnabled(false) + plugin.autobutcher_sortWatchList() + else + plugin.autobutcher_setEnabled(true) + end + self:initListChoices() + self:updateBottom() +end + +function WatchList:onToggleAutowatch() + if(plugin.autowatch_isEnabled()) then + plugin.autowatch_setEnabled(false) + else + plugin.autowatch_setEnabled(true) + end + self:initListChoices() + self:updateBottom() +end + + +local screen = WatchList{ } +screen:show() From 024a3d766b936e96d7f921146c3a6590f3101b3b Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Sat, 6 Apr 2013 15:10:13 +0200 Subject: [PATCH 71/73] Added some more commands to autobutcher UI script. Changed autobutcher behavior a bit: now protected units count towards the target quota, units available for adoption are considered as protected. Added filters for caged, male and female to assignement UI for cages, pastures and pits. --- dfhack.init-example | 3 + plugins/zone.cpp | 701 +++++++++++++++++++++--------- scripts/gui/autobutcher.lua | 836 +++++++++++++++++++++--------------- 3 files changed, 1005 insertions(+), 535 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index ddf93de16..6d73d86f3 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -93,6 +93,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow keybinding add Alt-W@overallstatus "gui/workflow status" +# autobutcher front-end +keybinding add Shift-B@pet/List/Unit "gui/autobutcher" + # assign weapon racks to squads so that they can be used keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 8907e507b..4b4ec5d9a 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -489,6 +489,24 @@ bool isHunter(df::unit* unit) return false; } +// check if unit is marked as available for adoption +bool isAvailableForAdoption(df::unit* unit) +{ + auto refs = unit->specific_refs; + for(int i=0; itype; + if( reftype == df::specific_ref_type::PETINFO_PET ) + { + //df::pet_info* pet = ref->pet; + return true; + } + } + + return false; +} + // check if creature belongs to the player's civilization // (don't try to pasture/slaughter random untame animals) bool isOwnCiv(df::unit* unit) @@ -503,6 +521,8 @@ bool isOwnRace(df::unit* unit) return unit->race == ui->race_id; } +// get race name by id or unit pointer +// todo: rename these two functions to "getRaceToken" since the output is more of a token string getRaceName(int32_t id) { df::creature_raw *raw = df::global::world->raws.creatures.all[id]; @@ -513,6 +533,15 @@ string getRaceName(df::unit* unit) df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; return raw->creature_id; } + +// get plural of race name (used for display in autobutcher UI and for sorting the watchlist) +string getRaceNamePlural(int32_t id) +{ + //WatchedRace * w = watched_races[idx]; + df::creature_raw *raw = df::global::world->raws.creatures.all[id]; + return raw->name[1]; // second field is plural of race name +} + string getRaceBabyName(df::unit* unit) { df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; @@ -2738,11 +2767,19 @@ public: bool isWatched; // if true, autobutcher will process this race int raceId; + + // target amounts int fk; // max female kids int mk; // max male kids int fa; // max female adults int ma; // max male adults + // amounts of protected (not butcherable) units + int fk_prot; + int fa_prot; + int mk_prot; + int ma_prot; + // bah, this should better be an array of 4 vectors // that way there's no need for the 4 ugly process methods vector fk_ptr; @@ -2758,6 +2795,7 @@ public: mk = _mk; fa = _fa; ma = _ma; + fk_prot = fa_prot = mk_prot = ma_prot = 0; } ~WatchedRace() @@ -2822,8 +2860,27 @@ public: } } + void PushProtectedUnit(df::unit * unit) + { + if(isFemale(unit)) + { + if(isBaby(unit) || isChild(unit)) + fk_prot++; + else + fa_prot++; + } + else //treat sex n/a like it was male + { + if(isBaby(unit) || isChild(unit)) + mk_prot++; + else + ma_prot++; + } + } + void ClearUnits() { + fk_prot = fa_prot = mk_prot = ma_prot = 0; fk_ptr.clear(); mk_ptr.clear(); fa_ptr.clear(); @@ -2833,7 +2890,7 @@ public: int ProcessUnits_fk() { int subcount = 0; - while(fk_ptr.size() > fk) + while(fk_ptr.size() && (fk_ptr.size() + fk_prot > fk) ) { df::unit* unit = fk_ptr.back(); doMarkForSlaughter(unit); @@ -2846,7 +2903,7 @@ public: int ProcessUnits_mk() { int subcount = 0; - while(mk_ptr.size() > mk) + while(mk_ptr.size() && (mk_ptr.size() + mk_prot > mk) ) { df::unit* unit = mk_ptr.back(); doMarkForSlaughter(unit); @@ -2859,7 +2916,7 @@ public: int ProcessUnits_fa() { int subcount = 0; - while(fa_ptr.size() > fa) + while(fa_ptr.size() && (fa_ptr.size() + fa_prot > fa) ) { df::unit* unit = fa_ptr.back(); doMarkForSlaughter(unit); @@ -2872,7 +2929,7 @@ public: int ProcessUnits_ma() { int subcount = 0; - while(ma_ptr.size() > ma) + while(ma_ptr.size() && (ma_ptr.size() + ma_prot > ma) ) { df::unit* unit = ma_ptr.back(); doMarkForSlaughter(unit); @@ -2902,8 +2959,8 @@ std::vector watched_races; // helper for sorting the watchlist alphabetically bool compareRaceNames(WatchedRace* i, WatchedRace* j) { - string name_i = getRaceName(i->raceId); - string name_j = getRaceName(j->raceId); + string name_i = getRaceNamePlural(i->raceId); + string name_j = getRaceNamePlural(j->raceId); return (name_i < name_j); } @@ -3347,6 +3404,12 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) for(size_t i=0; iunits.all.size(); i++) { df::unit * unit = world->units.all[i]; + + // this check is now divided into two steps, squeezed autowatch into the middle + // first one ignores completely inappropriate units (dead, undead, not belonging to the fort, ...) + // then let autowatch add units to the watchlist which will probably start breeding (owned pets, war animals, ...) + // then process units counting those which can't be butchered (war animals, named pets, ...) + // so that they are treated as "own stock" as well and count towards the target quota if( isDead(unit) || isUndead(unit) || isMarkedForSlaughter(unit) @@ -3354,12 +3417,6 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) || isForest(unit) // ignore merchants' caged animals || !isOwnCiv(unit) || !isTame(unit) - || isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || unit->name.has_name ) continue; @@ -3368,26 +3425,40 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) if(!isContainedInItem(unit) && !hasValidMapPos(unit)) continue; + WatchedRace * w = NULL; int watched_index = getWatchedIndex(unit->race); if(watched_index != -1) { - WatchedRace * w = watched_races[watched_index]; - if(w->isWatched) - w->PushUnit(unit); + w = watched_races[watched_index]; } else if(enable_autobutcher_autowatch) { - WatchedRace * w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); + w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); w->UpdateConfig(out); watched_races.push_back(w); - w->PushUnit(unit); string announce; - announce = "New race added to autobutcher watchlist: " + getRaceName(w->raceId); + announce = "New race added to autobutcher watchlist: " + getRaceNamePlural(w->raceId); Gui::showAnnouncement(announce, 2, false); - //out << announce << endl; autobutcher_sortWatchList(out); } + + if(w && w->isWatched) + { + // don't butcher protected units, but count them as stock as well + // this way they count towards target quota, so if you order that you want 1 female adult cat + // and have 2 cats, one of them being a pet, the other gets butchered + if( isWar(unit) // ignore war dogs etc + || isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || isAvailableForAdoption(unit) + || unit->name.has_name ) + w->PushProtectedUnit(unit); + else + w->PushUnit(unit); + } } int slaughter_count = 0; @@ -3401,12 +3472,10 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) stringstream ss; ss << slaughter_subcount; string announce; - announce = getRaceName(w->raceId) + " marked for slaughter: " + ss.str(); + announce = getRaceNamePlural(w->raceId) + " marked for slaughter: " + ss.str(); Gui::showAnnouncement(announce, 2, false); - //out << announce << endl; } } - //out << slaughter_count << " units total marked for slaughter." << endl; return CR_OK; } @@ -3490,7 +3559,6 @@ command_result init_autobutcher(color_ostream &out) //out << " mk: " << p->ival(3) << endl; //out << " fa: " << p->ival(4) << endl; //out << " ma: " << p->ival(5) << endl; - WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5)); w->rconfig = *p; watched_races.push_back(w); @@ -3562,6 +3630,199 @@ command_result cleanup_autonestbox(color_ostream &out) return CR_OK; } +// abuse WatchedRace struct for counting stocks (since it sorts by gender and age) +// calling method must delete pointer! +WatchedRace * checkRaceStocksTotal(int race) +{ + WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); + + for(size_t i=0; iunits.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + + if(unit->race != race) + continue; + + if( isDead(unit) + || isUndead(unit) + || isMerchant(unit) // ignore merchants' draught animals + || isForest(unit) // ignore merchants' caged animals + || !isOwnCiv(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksProtected(int race) +{ + WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); + + for(size_t i=0; iunits.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + + if(unit->race != race) + continue; + + if( isDead(unit) + || isUndead(unit) + || isMerchant(unit) // ignore merchants' draught animals + || isForest(unit) // ignore merchants' caged animals + || !isOwnCiv(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if( !isTame(unit) + || isWar(unit) // ignore war dogs etc + || isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || isAvailableForAdoption(unit) + || unit->name.has_name ) + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherable(int race) +{ + WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); + + for(size_t i=0; iunits.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + + if(unit->race != race) + continue; + + if( isDead(unit) + || isUndead(unit) + || isMerchant(unit) // ignore merchants' draught animals + || isForest(unit) // ignore merchants' caged animals + || !isOwnCiv(unit) + || !isTame(unit) + || isWar(unit) // ignore war dogs etc + || isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherFlag(int race) +{ + WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); + + for(size_t i=0; iunits.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + + if(unit->race != race) + continue; + + if( isDead(unit) + || isUndead(unit) + || isMerchant(unit) // ignore merchants' draught animals + || isForest(unit) // ignore merchants' caged animals + || !isOwnCiv(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if(isMarkedForSlaughter(unit)) + w->PushUnit(unit); + } + return w; +} + +void butcherRace(int race) +{ + for(size_t i=0; iunits.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + + if(unit->race != race) + continue; + + if( isDead(unit) + || isUndead(unit) + || isMerchant(unit) // ignore merchants' draught animals + || isForest(unit) // ignore merchants' caged animals + || !isOwnCiv(unit) + || !isTame(unit) + || isWar(unit) // ignore war dogs etc + || isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + unit->flags2.bits.slaughter = true; + } +} + +// remove butcher flag for all units of a given race +void unbutcherRace(int race) +{ + for(size_t i=0; iunits.all.size(); i++) + { + df::unit * unit = world->units.all[i]; + + if(unit->race != race) + continue; + + if( isDead(unit) + || isUndead(unit) + || !isMarkedForSlaughter(unit) + ) + continue; + + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + unit->flags2.bits.slaughter = false; + } +} + + ///////////////////////////////////// // API functions to control autobutcher with a lua script @@ -3615,135 +3876,48 @@ static void autowatch_setEnabled(color_ostream &out, bool enable) } } -static size_t autobutcher_getWatchListSize() -{ - return watched_races.size(); -} - -// get race name for a watchlist index -static std::string autobutcher_getWatchListRace(color_ostream &out, size_t idx) -{ - if(idx >= watched_races.size()) - return "INVALID"; - - WatchedRace * w = watched_races[idx]; - return getRaceName(w->raceId); -} - -// get FK for a watchlist index -static size_t autobutcher_getWatchListRaceFK(color_ostream &out, size_t idx) -{ - if(idx >= watched_races.size()) - return -1; - - WatchedRace * w = watched_races[idx]; - return w->fk; -} - -// get FA for a watchlist index -static size_t autobutcher_getWatchListRaceFA(color_ostream &out, size_t idx) +// set all data for a watchlist race in one go +// if race is not already on watchlist it will be added +// params: (id, fk, mk, fa, ma, watched) +static void autobutcher_setWatchListRace(color_ostream &out, size_t id, size_t fk, size_t mk, size_t fa, size_t ma, bool watched) { - if(idx >= watched_races.size()) - return -1; - - WatchedRace * w = watched_races[idx]; - return w->fa; -} - -// get MK for a watchlist index -static size_t autobutcher_getWatchListRaceMK(color_ostream &out, size_t idx) -{ - if(idx >= watched_races.size()) - return -1; - - WatchedRace * w = watched_races[idx]; - return w->mk; -} - -// get MA for a watchlist index -static size_t autobutcher_getWatchListRaceMA(color_ostream &out, size_t idx) -{ - if(idx >= watched_races.size()) - return -1; - - WatchedRace * w = watched_races[idx]; - return w->ma; -} - -// set FK for a watchlist index -static void autobutcher_setWatchListRaceFK(color_ostream &out, size_t idx, size_t value) -{ - if(idx >= watched_races.size()) - return; - - WatchedRace * w = watched_races[idx]; - w->fk = value; - w->UpdateConfig(out); -} - -// set FA for a watchlist index -static void autobutcher_setWatchListRaceFA(color_ostream &out, size_t idx, size_t value) -{ - if(idx >= watched_races.size()) - return; - - WatchedRace * w = watched_races[idx]; - w->fa = value; - w->UpdateConfig(out); -} - -// set MK for a watchlist index -static void autobutcher_setWatchListRaceMK(color_ostream &out, size_t idx, size_t value) -{ - if(idx >= watched_races.size()) - return; - - WatchedRace * w = watched_races[idx]; - w->mk = value; - w->UpdateConfig(out); -} - -// set MA for a watchlist index -static void autobutcher_setWatchListRaceMA(color_ostream &out, size_t idx, size_t value) -{ - if(idx >= watched_races.size()) - return; - - WatchedRace * w = watched_races[idx]; - w->ma = value; - w->UpdateConfig(out); -} - -// check if "watch" is enabled for watchlist index -static bool autobutcher_isWatchListRaceWatched(color_ostream &out, size_t idx) -{ - if(idx >= watched_races.size()) - return false; - - WatchedRace * w = watched_races[idx]; - return w->isWatched; -} - -// set "watched" status for a watchlist index -static void autobutcher_setWatchListRaceWatched(color_ostream &out, size_t idx, bool watched) -{ - if(idx >= watched_races.size()) - return; + int watched_index = getWatchedIndex(id); + if(watched_index != -1) + { + out << "updating watchlist entry" << endl; + WatchedRace * w = watched_races[watched_index]; + w->fk = fk; + w->mk = mk; + w->fa = fa; + w->ma = ma; + w->isWatched = watched; + w->UpdateConfig(out); + } + else + { + out << "creating new watchlist entry" << endl; + WatchedRace * w = new WatchedRace(watched, id, fk, mk, fa, ma); //default_fk, default_mk, default_fa, default_ma); + w->UpdateConfig(out); + watched_races.push_back(w); - WatchedRace * w = watched_races[idx]; - w->isWatched = watched; - w->UpdateConfig(out); + string announce; + announce = "New race added to autobutcher watchlist: " + getRaceNamePlural(w->raceId); + Gui::showAnnouncement(announce, 2, false); + autobutcher_sortWatchList(out); + } } // remove entry from watchlist -static void autobutcher_removeFromWatchList(color_ostream &out, size_t idx) +static void autobutcher_removeFromWatchList(color_ostream &out, size_t id) { - if(idx >= watched_races.size()) - return; - - WatchedRace * w = watched_races[idx]; - w->RemoveConfig(out); - watched_races.erase(watched_races.begin()+idx); + int watched_index = getWatchedIndex(id); + if(watched_index != -1) + { + out << "updating watchlist entry" << endl; + WatchedRace * w = watched_races[watched_index]; + w->RemoveConfig(out); + watched_races.erase(watched_races.begin() + watched_index); + } } // sort watchlist alphabetically @@ -3752,24 +3926,6 @@ static void autobutcher_sortWatchList(color_ostream &out) sort(watched_races.begin(), watched_races.end(), compareRaceNames); } -// get default target values for new races -static size_t autobutcher_getDefaultMK(color_ostream &out) -{ - return default_mk; -} -static size_t autobutcher_getDefaultMA(color_ostream &out) -{ - return default_ma; -} -static size_t autobutcher_getDefaultFK(color_ostream &out) -{ - return default_fk; -} -static size_t autobutcher_getDefaultFA(color_ostream &out) -{ - return default_fa; -} - // set default target values for new races static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma) { @@ -3801,13 +3957,86 @@ static void autobutcher_setDefaultTargetAll(color_ostream &out, size_t fk, size_ autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); } -static std::string testString(color_ostream &out, size_t i) +static void autobutcher_butcherRace(color_ostream &out, size_t id) +{ + butcherRace(id); +} + +static void autobutcher_unbutcherRace(color_ostream &out, size_t id) +{ + unbutcherRace(id); +} + +// push autobutcher settings on lua stack +static int autobutcher_getSettings(lua_State *L) { - out << "where will this be written?" << std::endl; - return getRaceName(i); + color_ostream &out = *Lua::GetOutput(L); + lua_newtable(L); + int ctable = lua_gettop(L); + Lua::SetField(L, enable_autobutcher, ctable, "enable_autobutcher"); + Lua::SetField(L, enable_autobutcher_autowatch, ctable, "enable_autowatch"); + Lua::SetField(L, default_fk, ctable, "fk"); + Lua::SetField(L, default_mk, ctable, "mk"); + Lua::SetField(L, default_fa, ctable, "fa"); + Lua::SetField(L, default_ma, ctable, "ma"); + Lua::SetField(L, sleep_autobutcher, ctable, "sleep"); + return 1; } -// DFHACK_LUA_FUNCTION(autobutcher_setEnabled), +// push the watchlist vector as nested table on the lua stack +static int autobutcher_getWatchList(lua_State *L) +{ + color_ostream &out = *Lua::GetOutput(L); + lua_newtable(L); + + for(size_t i=0; iraceId, ctable, "id"); + Lua::SetField(L, w->isWatched, ctable, "watched"); + Lua::SetField(L, getRaceNamePlural(w->raceId), ctable, "name"); + Lua::SetField(L, w->fk, ctable, "fk"); + Lua::SetField(L, w->mk, ctable, "mk"); + Lua::SetField(L, w->fa, ctable, "fa"); + Lua::SetField(L, w->ma, ctable, "ma"); + + int id = w->raceId; + + w = checkRaceStocksTotal(id); + Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_total"); + Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_total"); + Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_total"); + Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_total"); + delete w; + + w = checkRaceStocksProtected(id); + Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_protected"); + Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_protected"); + Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_protected"); + Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_protected"); + delete w; + + w = checkRaceStocksButcherable(id); + Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_butcherable"); + Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_butcherable"); + Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_butcherable"); + Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_butcherable"); + delete w; + + w = checkRaceStocksButcherFlag(id); + Lua::SetField(L, w->fk_ptr.size(), ctable, "fk_butcherflag"); + Lua::SetField(L, w->mk_ptr.size(), ctable, "mk_butcherflag"); + Lua::SetField(L, w->fa_ptr.size(), ctable, "fa_butcherflag"); + Lua::SetField(L, w->ma_ptr.size(), ctable, "ma_butcherflag"); + delete w; + + lua_rawseti(L, -2, i+1); + } + + return 1; +} DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(autobutcher_isEnabled), @@ -3816,34 +4045,19 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(autowatch_setEnabled), DFHACK_LUA_FUNCTION(autobutcher_getSleep), DFHACK_LUA_FUNCTION(autobutcher_setSleep), - DFHACK_LUA_FUNCTION(autobutcher_getWatchListSize), - DFHACK_LUA_FUNCTION(autobutcher_getWatchListRace), - - DFHACK_LUA_FUNCTION(autobutcher_isWatchListRaceWatched), - DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceWatched), - - DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceFK), - DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceFA), - DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceMK), - DFHACK_LUA_FUNCTION(autobutcher_getWatchListRaceMA), - - DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceFK), - DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceFA), - DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceMK), - DFHACK_LUA_FUNCTION(autobutcher_setWatchListRaceMA), - - DFHACK_LUA_FUNCTION(autobutcher_getDefaultFK), - DFHACK_LUA_FUNCTION(autobutcher_getDefaultFA), - DFHACK_LUA_FUNCTION(autobutcher_getDefaultMK), - DFHACK_LUA_FUNCTION(autobutcher_getDefaultMA), - + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRace), DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), - + DFHACK_LUA_FUNCTION(autobutcher_butcherRace), + DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace), DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), DFHACK_LUA_FUNCTION(autobutcher_sortWatchList), + DFHACK_LUA_END +}; - DFHACK_LUA_FUNCTION(testString), +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(autobutcher_getSettings), + DFHACK_LUA_COMMAND(autobutcher_getWatchList), DFHACK_LUA_END }; @@ -3858,6 +4072,8 @@ using df::global::ui_building_assign_is_marked; using df::global::ui_building_assign_units; using df::global::ui_building_assign_items; +using df::global::ui_building_in_assign; + 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) @@ -3894,7 +4110,7 @@ public: } search_string.clear(); - show_non_grazers = show_pastured = show_other_zones = true; + show_non_grazers = show_pastured = show_noncaged = show_male = show_female = show_other_zones = true; entry_mode = false; initialized = true; @@ -3952,6 +4168,22 @@ public: if (!show_pastured && isAssignedToZone(curr_unit)) continue; + if (!show_noncaged) + { + // must be in a container + if(!isContainedInItem(curr_unit)) + continue; + // but exclude built cages (zoos, traps, ...) to avoid "accidental" pitting of creatures you'd prefer to keep + if (isInBuiltCage(curr_unit)) + continue; + } + + if (!show_male && isMale(curr_unit)) + continue; + + if (!show_female && isFemale(curr_unit)) + continue; + if (!search_string_l.empty()) { string desc = Translation::TranslateName( @@ -4018,16 +4250,36 @@ public: } } // Not in query typing mode - else if (input->count(interface_key::CUSTOM_G) && mode == ui_sidebar_mode::ZonesPenInfo) + else if (input->count(interface_key::CUSTOM_G) && + (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::QueryBuilding)) { show_non_grazers = !show_non_grazers; apply_filters(); } - else if (input->count(interface_key::CUSTOM_P) && mode == ui_sidebar_mode::ZonesPenInfo) + else if (input->count(interface_key::CUSTOM_C) && + (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) + { + show_noncaged = !show_noncaged; + apply_filters(); + } + else if (input->count(interface_key::CUSTOM_P) && + (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) { show_pastured = !show_pastured; apply_filters(); } + else if (input->count(interface_key::CUSTOM_M) && + (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) + { + show_male = !show_male; + apply_filters(); + } + else if (input->count(interface_key::CUSTOM_F) && + (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) + { + show_female = !show_female; + apply_filters(); + } else if (input->count(interface_key::CUSTOM_S)) { // Hotkey pressed, enter typing mode @@ -4076,7 +4328,7 @@ public: OutputString(COLOR_LIGHTGREEN, x, y, "_"); } - if (mode == ui_sidebar_mode::ZonesPenInfo) + if (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::QueryBuilding) { x = left_margin; y += 2; @@ -4084,11 +4336,57 @@ public: 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, "c"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_noncaged) ? COLOR_WHITE : COLOR_GREY, x, y, "Not Caged"); + + 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"); + + x = left_margin; + ++y; + OutputString(COLOR_LIGHTGREEN, x, y, "f"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_female) ? COLOR_WHITE : COLOR_GREY, x, y, "Female"); + + x = left_margin; + ++y; + OutputString(COLOR_LIGHTGREEN, x, y, "m"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_male) ? COLOR_WHITE : COLOR_GREY, x, y, "Male"); + } + + // pits don't have grazer filter because it seems pointless + if (mode == ui_sidebar_mode::ZonesPitInfo) + { + x = left_margin; + y += 2; + OutputString(COLOR_LIGHTGREEN, x, y, "c"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_noncaged) ? COLOR_WHITE : COLOR_GREY, x, y, "Not Caged"); + 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"); + + x = left_margin; + ++y; + OutputString(COLOR_LIGHTGREEN, x, y, "f"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_female) ? COLOR_WHITE : COLOR_GREY, x, y, "Female"); + + x = left_margin; + ++y; + OutputString(COLOR_LIGHTGREEN, x, y, "m"); + OutputString(COLOR_WHITE, x, y, ": "); + OutputString((show_male) ? COLOR_WHITE : COLOR_GREY, x, y, "Male"); } } @@ -4097,7 +4395,7 @@ private: string search_string; bool initialized; bool entry_mode; - bool show_non_grazers, show_pastured, show_other_zones; + bool show_non_grazers, show_pastured, show_noncaged, show_male, show_female, show_other_zones; std::vector saved_ui_building_assign_type; std::vector saved_ui_building_assign_units, reference_list; @@ -4121,11 +4419,20 @@ struct zone_hook : public df::viewscreen_dwarfmodest DEFINE_VMETHOD_INTERPOSE(void, render, ()) { - if ((ui->main.mode == ui_sidebar_mode::ZonesPenInfo || ui->main.mode == ui_sidebar_mode::ZonesPitInfo) && + 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) + // allow mode QueryBuilding, but only for cages (bedrooms will crash DF with this code, chains don't work either etc) + || + ( ui->main.mode == ui_sidebar_mode::QueryBuilding && + ui_building_in_assign && *ui_building_in_assign && + ui_building_assign_type && ui_building_assign_units && + ui_building_assign_type->size() == ui_building_assign_units->size() && + ui_building_item_cursor && + world->selected_building && isCage(world->selected_building) ) + ) { if (vector_get(*ui_building_assign_units, *ui_building_item_cursor)) filter.initialize(ui->main.mode); diff --git a/scripts/gui/autobutcher.lua b/scripts/gui/autobutcher.lua index 1c62775ba..013406f33 100644 --- a/scripts/gui/autobutcher.lua +++ b/scripts/gui/autobutcher.lua @@ -1,45 +1,11 @@ -- A GUI front-end for the autobutcher plugin. --- requires to be called from the stock screen (z) - - ---[[ - - API overview (zone/autobutcher plugin functions which can be used by this lua script): - - autobutcher_isEnabled() - returns true if autobutcher is running - autowatch_isEnabled() - returns true if autowatch is running - autobutcher_getSleep() - get sleep timer in ticks - autobutcher_setSleep(int) - set sleep timer in ticks - autobutcher_getWatchListSize() - return size of watchlist - autobutcher_getWatchListRace(idx) - return race name for this watchlist index - - autobutcher_isWatchListRaceWatched(idx) - true if this watchlist index is watched - autobutcher_setWatchListRaceWatched(idx, bool) - set watchlist index to watched/unwatched - - autobutcher_getWatchListRaceFK() - get target fk - autobutcher_getWatchListRaceFA() - get target fa - autobutcher_getWatchListRaceMK() - get target mk - autobutcher_getWatchListRaceMA() - get target ma - - autobutcher_setWatchListRaceFK(value) - set fk - autobutcher_setWatchListRaceFA(value) - set fa - autobutcher_setWatchListRaceMK(value) - set mk - autobutcher_setWatchListRaceMA(value) - set ma - - autobutcher_removeFromWatchList(idx) - remove watchlist entry - autobutcher_sortWatchList() - sort the watchlist alphabetically - - testString(id) - returns race name for this race id (gonna be removed soon) - ---]] - local gui = require 'gui' local utils = require 'utils' local widgets = require 'gui.widgets' local dlg = require 'gui.dialogs' -local plugin = require 'plugins.zone' +local plugin = require 'plugins.zone' WatchList = defclass(WatchList, gui.FramedScreen) @@ -54,28 +20,29 @@ WatchList.ATTRS { local racewidth = 25 function nextAutowatchState() - if(plugin.autowatch_isEnabled()) then - return 'stop' - end - return 'start' + if(plugin.autowatch_isEnabled()) then + return 'Stop ' + end + return 'Start' end function nextAutobutcherState() - if(plugin.autobutcher_isEnabled()) then - return 'stop' - end - return 'start' + if(plugin.autobutcher_isEnabled()) then + return 'Stop ' + end + return 'Start' end function getSleepTimer() - return plugin.autobutcher_getSleep() + return plugin.autobutcher_getSleep() end function setSleepTimer(ticks) - plugin.autobutcher_setSleep(ticks) + plugin.autobutcher_setSleep(ticks) end function WatchList:init(args) + local colwidth = 7 self:addviews{ widgets.Panel{ frame = { l = 0, r = 0 }, @@ -86,30 +53,33 @@ function WatchList:init(args) text_pen = COLOR_CYAN, text = { { text = 'Race', width = racewidth }, ' ', - { text = 'female', width = 6 }, ' ', - { text = ' male', width = 6 }, ' ', - { text = 'Female', width = 6 }, ' ', - { text = ' Male', width = 6 }, ' ', - { text = 'watching?' }, - NEWLINE, + { text = 'female', width = colwidth }, ' ', + { text = ' male', width = colwidth }, ' ', + { text = 'Female', width = colwidth }, ' ', + { text = ' Male', width = colwidth }, ' ', + { text = 'watch? ' }, + { text = ' butchering' }, + NEWLINE, { text = '', width = racewidth }, ' ', - { text = ' kids', width = 6 }, ' ', - { text = ' kids', width = 6 }, ' ', - { text = 'adults', width = 6 }, ' ', - { text = 'adults', width = 6 }, ' ', + { text = ' kids', width = colwidth }, ' ', + { text = ' kids', width = colwidth }, ' ', + { text = 'adults', width = colwidth }, ' ', + { text = 'adults', width = colwidth }, ' ', + { text = ' ' }, + { text = ' ordered' }, } }, widgets.List{ view_id = 'list', - frame = { t = 3, b = 3 }, + frame = { t = 3, b = 5 }, not_found_label = 'Watchlist is empty.', edit_pen = COLOR_LIGHTCYAN, text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, - --on_select = self:callback('onSelectConstraint'), + --on_select = self:callback('onSelectEntry'), }, - widgets.Label{ - view_id = 'bottom_ui', + widgets.Label{ + view_id = 'bottom_ui', frame = { b = 0, h = 1 }, text = 'filled by updateBottom()' } @@ -117,92 +87,182 @@ function WatchList:init(args) }, } - self:initListChoices() - self:updateBottom() + self:initListChoices() + self:updateBottom() +end + +-- change the viewmode for stock data displayed in left section of columns +local viewmodes = { 'total stock', 'protected stock', 'butcherable', 'butchering ordered' } +local viewmode = 1 +function WatchList:onToggleView() + if viewmode < #viewmodes then + viewmode = viewmode + 1 + else + viewmode = 1 + end + self:initListChoices() + self:updateBottom() end -- update the bottom part of the UI (after sleep timer changed etc) function WatchList:updateBottom() - self.subviews.bottom_ui:setText( - { - { key = 'CUSTOM_F', text = ': f kids', - on_activate = self:callback('onEditFK') }, ', ', - { key = 'CUSTOM_M', text = ': m kids', - on_activate = self:callback('onEditMK') }, ', ', - { key = 'CUSTOM_SHIFT_F', text = ': f adults', - on_activate = self:callback('onEditFA') }, ', ', - { key = 'CUSTOM_SHIFT_M', text = ': m adults', - on_activate = self:callback('onEditMA') }, ', ', - { key = 'CUSTOM_SHIFT_X', text = ': Delete', - on_activate = self:callback('onDeleteConstraint') }, ', ', - { key = 'CUSTOM_W', text = ': toggle watch', - on_activate = self:callback('onToggleWatching') }, ', ', NEWLINE, - { key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher', - on_activate = self:callback('onToggleAutobutcher') }, ', ', - { key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch', - on_activate = self:callback('onToggleAutowatch') }, ', ', - { key = 'CUSTOM_SHIFT_S', text = ': sleep timer ('..getSleepTimer()..' ticks)', - on_activate = self:callback('onEditSleepTimer') }, ', ', - }) + self.subviews.bottom_ui:setText( + { + { key = 'CUSTOM_SHIFT_V', text = ': View in colums shows: '..viewmodes[viewmode]..' / target max', + on_activate = self:callback('onToggleView') }, NEWLINE, + { key = 'CUSTOM_F', text = ': f kids', + on_activate = self:callback('onEditFK') }, ', ', + { key = 'CUSTOM_M', text = ': m kids', + on_activate = self:callback('onEditMK') }, ', ', + { key = 'CUSTOM_SHIFT_F', text = ': f adults', + on_activate = self:callback('onEditFA') }, ', ', + { key = 'CUSTOM_SHIFT_M', text = ': m adults', + on_activate = self:callback('onEditMA') }, '. ', + { key = 'CUSTOM_W', text = ': Toggle watch', + on_activate = self:callback('onToggleWatching') }, '. ', + { key = 'CUSTOM_X', text = ': Delete', + on_activate = self:callback('onDeleteEntry') }, '. ', NEWLINE, + --{ key = 'CUSTOM_A', text = ': Add race', + -- on_activate = self:callback('onAddRace') }, ', ', + { key = 'CUSTOM_SHIFT_R', text = ': Set whole row', + on_activate = self:callback('onSetRow') }, '. ', + { key = 'CUSTOM_B', text = ': Remove butcher orders', + on_activate = self:callback('onUnbutcherRace') }, '. ', + { key = 'CUSTOM_SHIFT_B', text = ': Butcher race', + on_activate = self:callback('onButcherRace') }, '. ', NEWLINE, + { key = 'CUSTOM_SHIFT_A', text = ': '..nextAutobutcherState()..' Autobutcher', + on_activate = self:callback('onToggleAutobutcher') }, '. ', + { key = 'CUSTOM_SHIFT_W', text = ': '..nextAutowatchState()..' Autowatch', + on_activate = self:callback('onToggleAutowatch') }, '. ', + { key = 'CUSTOM_SHIFT_S', text = ': Sleep ('..getSleepTimer()..' ticks)', + on_activate = self:callback('onEditSleepTimer') }, '. ', + }) +end + +function stringify(number) + -- cap displayed number to 3 digits + -- after population of 50 per race is reached pets stop breeding anyways + -- so probably this could safely be reduced to 99 + local max = 999 + if number > max then number = max end + return tostring(number) end function WatchList:initListChoices() - local choices = {} - - -- first two rows are for "edit all races" and "edit new races" - local fk = plugin.autobutcher_getDefaultFK() - local fa = plugin.autobutcher_getDefaultFA() - local mk = plugin.autobutcher_getDefaultMK() - local ma = plugin.autobutcher_getDefaultMA() - local watched = '---' - - table.insert (choices, { - text = { - { text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ', - { text = tostring(fk), width = 7, rjustify = true }, - { text = tostring(mk), width = 7, rjustify = true }, - { text = tostring(fa), width = 7, rjustify = true }, - { text = tostring(ma), width = 7, rjustify = true }, - { text = watched, width = 6, rjustify = true } - } - }) - - table.insert (choices, { - text = { - { text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ', - { text = tostring(fk), width = 7, rjustify = true }, - { text = tostring(mk), width = 7, rjustify = true }, - { text = tostring(fa), width = 7, rjustify = true }, - { text = tostring(ma), width = 7, rjustify = true }, - { text = watched, width = 6, rjustify = true } - } - }) - - -- fill with watchlist - for i=0, plugin.autobutcher_getWatchListSize()-1 do - local racestr = plugin.autobutcher_getWatchListRace(i) - fk = plugin.autobutcher_getWatchListRaceFK(i) - fa = plugin.autobutcher_getWatchListRaceFA(i) - mk = plugin.autobutcher_getWatchListRaceMK(i) - ma = plugin.autobutcher_getWatchListRaceMA(i) - local watched = 'no' - if plugin.autobutcher_isWatchListRaceWatched(i) then - watched = 'yes' - end - - table.insert (choices, { - text = { + local choices = {} + + -- first two rows are for "edit all races" and "edit new races" + local settings = plugin.autobutcher_getSettings() + local fk = stringify(settings.fk) + local fa = stringify(settings.fa) + local mk = stringify(settings.mk) + local ma = stringify(settings.ma) + + local watched = '' + + local colwidth = 7 + + table.insert (choices, { + text = { + { text = '!! ALL RACES PLUS NEW', width = racewidth, pad_char = ' ' }, --' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = watched, width = 6, rjustify = true } + } + }) + + table.insert (choices, { + text = { + { text = '!! ONLY NEW RACES', width = racewidth, pad_char = ' ' }, --' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = fk, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = mk, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = fa, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = ' ', width = 3, rjustify = true, pad_char = ' ' }, ' ', + { text = ma, width = 3, rjustify = false, pad_char = ' ' }, ' ', + { text = watched, width = 6, rjustify = true } + } + }) + + local watchlist = plugin.autobutcher_getWatchList() + + for i,entry in ipairs(watchlist) do + fk = stringify(entry.fk) + fa = stringify(entry.fa) + mk = stringify(entry.mk) + ma = stringify(entry.ma) + if viewmode == 1 then + fkc = stringify(entry.fk_total) + fac = stringify(entry.fa_total) + mkc = stringify(entry.mk_total) + mac = stringify(entry.ma_total) + end + if viewmode == 2 then + fkc = stringify(entry.fk_protected) + fac = stringify(entry.fa_protected) + mkc = stringify(entry.mk_protected) + mac = stringify(entry.ma_protected) + end + if viewmode == 3 then + fkc = stringify(entry.fk_butcherable) + fac = stringify(entry.fa_butcherable) + mkc = stringify(entry.mk_butcherable) + mac = stringify(entry.ma_butcherable) + end + if viewmode == 4 then + fkc = stringify(entry.fk_butcherflag) + fac = stringify(entry.fa_butcherflag) + mkc = stringify(entry.mk_butcherflag) + mac = stringify(entry.ma_butcherflag) + end + local butcher_ordered = entry.fk_butcherflag + entry.fa_butcherflag + entry.mk_butcherflag + entry.ma_butcherflag + local bo = ' ' + if butcher_ordered > 0 then bo = stringify(butcher_ordered) end + + local watched = 'no' + if entry.watched then watched = 'yes' end + + local racestr = entry.name + + -- highlight entries where the target quota can't be met because too many are protected + bad_pen = COLOR_LIGHTRED + good_pen = NONE -- this is stupid, but it works. sue me + fk_pen = good_pen + fa_pen = good_pen + mk_pen = good_pen + ma_pen = good_pen + if entry.fk_protected > entry.fk then fk_pen = bad_pen end + if entry.fa_protected > entry.fa then fa_pen = bad_pen end + if entry.mk_protected > entry.mk then mk_pen = bad_pen end + if entry.ma_protected > entry.ma then ma_pen = bad_pen end + + table.insert (choices, { + text = { { text = racestr, width = racewidth, pad_char = ' ' }, --' ', - { text = tostring(fk), width = 7, rjustify = true }, - { text = tostring(mk), width = 7, rjustify = true }, - { text = tostring(fa), width = 7, rjustify = true }, - { text = tostring(ma), width = 7, rjustify = true }, - { text = watched, width = 6, rjustify = true } - } - }) - end - + { text = fkc, width = 3, rjustify = true, pad_char = ' ' }, '/', + { text = fk, width = 3, rjustify = false, pad_char = ' ', pen = fk_pen }, ' ', + { text = mkc, width = 3, rjustify = true, pad_char = ' ' }, '/', + { text = mk, width = 3, rjustify = false, pad_char = ' ', pen = mk_pen }, ' ', + { text = fac, width = 3, rjustify = true, pad_char = ' ' }, '/', + { text = fa, width = 3, rjustify = false, pad_char = ' ', pen = fa_pen }, ' ', + { text = mac, width = 3, rjustify = true, pad_char = ' ' }, '/', + { text = ma, width = 3, rjustify = false, pad_char = ' ', pen = ma_pen }, ' ', + { text = watched, width = 6, rjustify = true, pad_char = ' ' }, ' ', + { text = bo, width = 8, rjustify = true, pad_char = ' ' } + }, + obj = entry, + }) + end + local list = self.subviews.list list:setChoices(choices) end @@ -210,285 +270,385 @@ end function WatchList:onInput(keys) if keys.LEAVESCREEN then self:dismiss() - else - WatchList.super.onInput(self, keys) + else + WatchList.super.onInput(self, keys) end end -- check the user input for target population values function WatchList:checkUserInput(count, text) - if count == nil then - dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) - return false - end - if count < 0 then - dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED) - return false - end - return true + if count == nil then + dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) + return false + end + if count < 0 then + dlg.showMessage('Invalid Number', 'Negative numbers make no sense!', COLOR_LIGHTRED) + return false + end + return true end -- check the user input for sleep timer function WatchList:checkUserInputSleep(count, text) - if count == nil then - dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) - return false - end - if count < 1000 then - dlg.showMessage('Invalid Number', - 'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!', - COLOR_LIGHTRED) - return false - end - return true + if count == nil then + dlg.showMessage('Invalid Number', 'This is not a number: '..text..NEWLINE..'(for zero enter a 0)', COLOR_LIGHTRED) + return false + end + if count < 1000 then + dlg.showMessage('Invalid Number', + 'Minimum allowed timer value is 1000!'..NEWLINE..'Too low values could decrease performance'..NEWLINE..'and are not necessary!', + COLOR_LIGHTRED) + return false + end + return true end function WatchList:onEditFK() - local selidx,selobj = self.subviews.list:getSelected() - local fk = plugin.autobutcher_getDefaultFK(); - local mk = plugin.autobutcher_getDefaultMK(); - local fa = plugin.autobutcher_getDefaultFA(); - local ma = plugin.autobutcher_getDefaultMA(); - local race = 'ALL RACES PLUS NEW' - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - local watchindex = selidx - 3 - if selidx > 2 then - fk = plugin.autobutcher_getWatchListRaceFK(watchindex) - race = plugin.autobutcher_getWatchListRace(watchindex) - end - - dlg.showInputPrompt( + local selidx,selobj = self.subviews.list:getSelected() + local settings = plugin.autobutcher_getSettings() + local fk = settings.fk + local mk = settings.mk + local fa = settings.fa + local ma = settings.ma + local race = 'ALL RACES PLUS NEW' + local id = -1 + local watched = false + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + if selidx > 2 then + local entry = selobj.obj + fk = entry.fk + mk = entry.mk + fa = entry.fa + ma = entry.ma + race = entry.name + id = entry.id + watched = entry.watched + end + + dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of female kids:', COLOR_WHITE, ' '..fk, function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - fk = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRaceFK(watchindex, fk) - end - self:initListChoices() - end + local count = tonumber(text) + if self:checkUserInput(count, text) then + fk = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) + end + self:initListChoices() + end end ) end function WatchList:onEditMK() - local selidx = self.subviews.list:getSelected() - local fk = plugin.autobutcher_getDefaultFK(); - local mk = plugin.autobutcher_getDefaultMK(); - local fa = plugin.autobutcher_getDefaultFA(); - local ma = plugin.autobutcher_getDefaultMA(); - local race = 'ALL RACES PLUS NEW' - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - local watchindex = selidx - 3 - if selidx > 2 then - mk = plugin.autobutcher_getWatchListRaceMK(watchindex) - race = plugin.autobutcher_getWatchListRace(watchindex) - end - - dlg.showInputPrompt( + local selidx,selobj = self.subviews.list:getSelected() + local settings = plugin.autobutcher_getSettings() + local fk = settings.fk + local mk = settings.mk + local fa = settings.fa + local ma = settings.ma + local race = 'ALL RACES PLUS NEW' + local id = -1 + local watched = false + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + if selidx > 2 then + local entry = selobj.obj + fk = entry.fk + mk = entry.mk + fa = entry.fa + ma = entry.ma + race = entry.name + id = entry.id + watched = entry.watched + end + + dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of male kids:', COLOR_WHITE, ' '..mk, function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - mk = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRaceMK(watchindex, mk) - end - self:initListChoices() - end + local count = tonumber(text) + if self:checkUserInput(count, text) then + mk = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) + end + self:initListChoices() + end end ) end function WatchList:onEditFA() - local selidx = self.subviews.list:getSelected() - local fk = plugin.autobutcher_getDefaultFK(); - local mk = plugin.autobutcher_getDefaultMK(); - local fa = plugin.autobutcher_getDefaultFA(); - local ma = plugin.autobutcher_getDefaultMA(); - local race = 'ALL RACES PLUS NEW' - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - local watchindex = selidx - 3 - if selidx > 2 then - fa = plugin.autobutcher_getWatchListRaceFA(watchindex) - race = plugin.autobutcher_getWatchListRace(watchindex) - end - - dlg.showInputPrompt( + local selidx,selobj = self.subviews.list:getSelected() + local settings = plugin.autobutcher_getSettings() + local fk = settings.fk + local mk = settings.mk + local fa = settings.fa + local ma = settings.ma + local race = 'ALL RACES PLUS NEW' + local id = -1 + local watched = false + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + if selidx > 2 then + local entry = selobj.obj + fk = entry.fk + mk = entry.mk + fa = entry.fa + ma = entry.ma + race = entry.name + id = entry.id + watched = entry.watched + end + + dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of female adults:', COLOR_WHITE, ' '..fa, function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - fa = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRaceFA(watchindex, fa) - end - self:initListChoices() - end + local count = tonumber(text) + if self:checkUserInput(count, text) then + fa = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) + end + self:initListChoices() + end end ) end function WatchList:onEditMA() - local selidx = self.subviews.list:getSelected() - local fk = plugin.autobutcher_getDefaultFK(); - local mk = plugin.autobutcher_getDefaultMK(); - local fa = plugin.autobutcher_getDefaultFA(); - local ma = plugin.autobutcher_getDefaultMA(); - local race = 'ALL RACES PLUS NEW' - - if selidx == 2 then - race = 'ONLY NEW RACES' - end - - local watchindex = selidx - 3 - if selidx > 2 then - ma = plugin.autobutcher_getWatchListRaceMA(watchindex) - race = plugin.autobutcher_getWatchListRace(watchindex) - end - - dlg.showInputPrompt( + local selidx,selobj = self.subviews.list:getSelected() + local settings = plugin.autobutcher_getSettings() + local fk = settings.fk + local mk = settings.mk + local fa = settings.fa + local ma = settings.ma + local race = 'ALL RACES PLUS NEW' + local id = -1 + local watched = false + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + if selidx > 2 then + local entry = selobj.obj + fk = entry.fk + mk = entry.mk + fa = entry.fa + ma = entry.ma + race = entry.name + id = entry.id + watched = entry.watched + end + + dlg.showInputPrompt( 'Race: '..race, 'Enter desired maximum of male adults:', COLOR_WHITE, ' '..ma, function(text) - local count = tonumber(text) - if self:checkUserInput(count, text) then - ma = count - if selidx == 1 then - plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) - end - if selidx == 2 then - plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) - end - if selidx > 2 then - plugin.autobutcher_setWatchListRaceMA(watchindex, ma) - end - self:initListChoices() - end + local count = tonumber(text) + if self:checkUserInput(count, text) then + ma = count + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( fk, mk, fa, ma ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( fk, mk, fa, ma ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRace(id, fk, mk, fa, ma, watched) + end + self:initListChoices() + end end ) end function WatchList:onEditSleepTimer() - local sleep = getSleepTimer() - dlg.showInputPrompt( + local sleep = getSleepTimer() + dlg.showInputPrompt( 'Edit Sleep Timer', 'Enter new sleep timer in ticks:'..NEWLINE..'(1 ingame day equals 1200 ticks)', COLOR_WHITE, ' '..sleep, function(text) - local count = tonumber(text) - if self:checkUserInputSleep(count, text) then - sleep = count - setSleepTimer(sleep) - self:updateBottom() - end + local count = tonumber(text) + if self:checkUserInputSleep(count, text) then + sleep = count + setSleepTimer(sleep) + self:updateBottom() + end end ) end function WatchList:onToggleWatching() - local selidx = self.subviews.list:getSelected() - if(selidx == 0) then - --print('special handling for zero - list empty?') - end - if (selidx == 1) then - --print('special handling for first row - ALL animals') - end - if (selidx == 2) then - --print('special handling for second row - NEW animals') - end - if selidx > 2 then - --print('handling for single animal on watchlist') - local idx = selidx - 3 - if plugin.autobutcher_isWatchListRaceWatched(idx) then - plugin.autobutcher_setWatchListRaceWatched(idx, false) - else - plugin.autobutcher_setWatchListRaceWatched(idx, true) - end - end - self:initListChoices() + local selidx,selobj = self.subviews.list:getSelected() + if selidx > 2 then + local entry = selobj.obj + plugin.autobutcher_setWatchListRace(entry.id, entry.fk, entry.mk, entry.fa, entry.ma, not entry.watched) + end + self:initListChoices() end -function WatchList:onDeleteConstraint() - local selidx,selobj = self.subviews.list:getSelected() - if(selidx < 3) then - -- print('cannot delete this entry') - return - end - local idx = selidx - 3 +function WatchList:onDeleteEntry() + local selidx,selobj = self.subviews.list:getSelected() + if(selidx < 3 or selobj == nil) then + return + end dlg.showYesNoPrompt( 'Delete from Watchlist', 'Really delete the selected entry?'..NEWLINE..'(you could just toggle watch instead)', COLOR_YELLOW, function() - plugin.autobutcher_removeFromWatchList(idx) + plugin.autobutcher_removeFromWatchList(selobj.obj.id) self:initListChoices() end - ) + ) +end + +function WatchList:onAddRace() + print('onAddRace - not implemented yet') +end + +function WatchList:onUnbutcherRace() + local selidx,selobj = self.subviews.list:getSelected() + if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end + if selidx > 2 then + local entry = selobj.obj + local race = entry.name + plugin.autobutcher_unbutcherRace(entry.id) + self:initListChoices() + self:updateBottom() + end +end + +function WatchList:onButcherRace() + local selidx,selobj = self.subviews.list:getSelected() + if selidx < 3 then dlg.showMessage('Error', 'Select a specific race.', COLOR_LIGHTRED) end + if selidx > 2 then + local entry = selobj.obj + local race = entry.name + plugin.autobutcher_butcherRace(entry.id) + self:initListChoices() + self:updateBottom() + end +end + +-- set whole row (fk, mk, fa, ma) to one value +function WatchList:onSetRow() + local selidx,selobj = self.subviews.list:getSelected() + local race = 'ALL RACES PLUS NEW' + local id = -1 + local watched = false + + if selidx == 2 then + race = 'ONLY NEW RACES' + end + + local watchindex = selidx - 3 + if selidx > 2 then + local entry = selobj.obj + race = entry.name + id = entry.id + watched = entry.watched + end + + dlg.showInputPrompt( + 'Set whole row for '..race, + 'Enter desired maximum for all subtypes:', + COLOR_WHITE, + ' ', + function(text) + local count = tonumber(text) + if self:checkUserInput(count, text) then + if selidx == 1 then + plugin.autobutcher_setDefaultTargetAll( count, count, count, count ) + end + if selidx == 2 then + plugin.autobutcher_setDefaultTargetNew( count, count, count, count ) + end + if selidx > 2 then + plugin.autobutcher_setWatchListRace(id, count, count, count, count, watched) + end + self:initListChoices() + end + end + ) end function WatchList:onToggleAutobutcher() - if(plugin.autobutcher_isEnabled()) then - plugin.autobutcher_setEnabled(false) - plugin.autobutcher_sortWatchList() - else - plugin.autobutcher_setEnabled(true) - end - self:initListChoices() - self:updateBottom() + if(plugin.autobutcher_isEnabled()) then + plugin.autobutcher_setEnabled(false) + plugin.autobutcher_sortWatchList() + else + plugin.autobutcher_setEnabled(true) + end + self:initListChoices() + self:updateBottom() end function WatchList:onToggleAutowatch() - if(plugin.autowatch_isEnabled()) then - plugin.autowatch_setEnabled(false) - else - plugin.autowatch_setEnabled(true) - end - self:initListChoices() - self:updateBottom() + if(plugin.autowatch_isEnabled()) then + plugin.autowatch_setEnabled(false) + else + plugin.autowatch_setEnabled(true) + end + self:initListChoices() + self:updateBottom() +end + +if not dfhack.isMapLoaded() then + qerror('Map is not loaded.') +end + +if string.match(dfhack.gui.getCurFocus(), '^dfhack/lua') then + qerror("This script must not be called while other lua gui stuff is running.") +end + +-- maybe this is too strict, there is not really a reason why it can only be called from the status screen +-- (other than the hotkey might overlap with other scripts) +if (not string.match(dfhack.gui.getCurFocus(), '^overallstatus') and not string.match(dfhack.gui.getCurFocus(), '^pet/List/Unit')) then + qerror("This script must either be called from the overall status screen or the animal list screen.") end From ce470331905e99f1e749ba9b60667287dd3a6ce1 Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 8 Apr 2013 06:38:36 +0200 Subject: [PATCH 72/73] fix in zone UI filters: lower case 'f' conflicted as hotkey with toggling between pit and pond in the pit screen. So now the filter keys use upper case letters instead. --- plugins/zone.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 4b4ec5d9a..5649da252 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -4250,31 +4250,31 @@ public: } } // Not in query typing mode - else if (input->count(interface_key::CUSTOM_G) && + else if (input->count(interface_key::CUSTOM_SHIFT_G) && (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::QueryBuilding)) { show_non_grazers = !show_non_grazers; apply_filters(); } - else if (input->count(interface_key::CUSTOM_C) && + else if (input->count(interface_key::CUSTOM_SHIFT_C) && (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) { show_noncaged = !show_noncaged; apply_filters(); } - else if (input->count(interface_key::CUSTOM_P) && + else if (input->count(interface_key::CUSTOM_SHIFT_P) && (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) { show_pastured = !show_pastured; apply_filters(); } - else if (input->count(interface_key::CUSTOM_M) && + else if (input->count(interface_key::CUSTOM_SHIFT_M) && (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) { show_male = !show_male; apply_filters(); } - else if (input->count(interface_key::CUSTOM_F) && + else if (input->count(interface_key::CUSTOM_SHIFT_F) && (mode == ui_sidebar_mode::ZonesPenInfo || mode == ui_sidebar_mode::ZonesPitInfo || mode == ui_sidebar_mode::QueryBuilding)) { show_female = !show_female; @@ -4332,31 +4332,31 @@ public: { x = left_margin; y += 2; - OutputString(COLOR_LIGHTGREEN, x, y, "g"); + 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, "c"); + OutputString(COLOR_LIGHTGREEN, x, y, "C"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_noncaged) ? COLOR_WHITE : COLOR_GREY, x, y, "Not Caged"); x = left_margin; ++y; - OutputString(COLOR_LIGHTGREEN, x, y, "p"); + OutputString(COLOR_LIGHTGREEN, x, y, "P"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_pastured) ? COLOR_WHITE : COLOR_GREY, x, y, "Currently Pastured"); x = left_margin; ++y; - OutputString(COLOR_LIGHTGREEN, x, y, "f"); + OutputString(COLOR_LIGHTGREEN, x, y, "F"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_female) ? COLOR_WHITE : COLOR_GREY, x, y, "Female"); x = left_margin; ++y; - OutputString(COLOR_LIGHTGREEN, x, y, "m"); + OutputString(COLOR_LIGHTGREEN, x, y, "M"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_male) ? COLOR_WHITE : COLOR_GREY, x, y, "Male"); } @@ -4366,25 +4366,25 @@ public: { x = left_margin; y += 2; - OutputString(COLOR_LIGHTGREEN, x, y, "c"); + OutputString(COLOR_LIGHTGREEN, x, y, "C"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_noncaged) ? COLOR_WHITE : COLOR_GREY, x, y, "Not Caged"); x = left_margin; ++y; - OutputString(COLOR_LIGHTGREEN, x, y, "p"); + OutputString(COLOR_LIGHTGREEN, x, y, "P"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_pastured) ? COLOR_WHITE : COLOR_GREY, x, y, "Currently Pastured"); x = left_margin; ++y; - OutputString(COLOR_LIGHTGREEN, x, y, "f"); + OutputString(COLOR_LIGHTGREEN, x, y, "F"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_female) ? COLOR_WHITE : COLOR_GREY, x, y, "Female"); x = left_margin; ++y; - OutputString(COLOR_LIGHTGREEN, x, y, "m"); + OutputString(COLOR_LIGHTGREEN, x, y, "M"); OutputString(COLOR_WHITE, x, y, ": "); OutputString((show_male) ? COLOR_WHITE : COLOR_GREY, x, y, "Male"); } From 300b24b401eb96edbcc6a9e5b518de2f0682f3e7 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Wed, 24 Apr 2013 23:47:37 +1200 Subject: [PATCH 73/73] Add example keybindings --- dfhack.init-example | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index 6d73d86f3..0391c2fd4 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -42,6 +42,13 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force" # Context-specific bindings # ############################# +# Stocks plugin +keybinding add Ctrl-Shift-Z@dwarfmode/Default "stocks show" + +# Workflow +keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some "gui/workflow" +keybinding add Ctrl-I "gui/workflow status" + # q->stockpile; p - copy & paste stockpiles keybinding add Alt-P copystock