From 481e5cc19ea1af2091b02f1eac11721b3de294d5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jan 2015 20:55:37 -0500 Subject: [PATCH 01/15] Implement dwarf selection --- plugins/manipulator.cpp | 93 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 56c1f8894..e9d918df3 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,8 @@ REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(enabler); +#define CONFIG_PATH "manipulator" + struct SkillLevel { const char *name; @@ -270,10 +273,12 @@ struct UnitInfo int active_index; string squad_effective_name; string squad_info; + bool selected; }; enum altsort_mode { ALTSORT_NAME, + ALTSORT_SELECTED, ALTSORT_PROFESSION_OR_SQUAD, ALTSORT_STRESS, ALTSORT_ARRIVAL, @@ -376,8 +381,14 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) return false; } +bool sortBySelected (const UnitInfo *d1, const UnitInfo *d2) +{ + return descending ? (d1->selected > d2->selected) : (d1->selected < d2->selected); +} + enum display_columns { DISP_COLUMN_STRESS, + DISP_COLUMN_SELECTED, DISP_COLUMN_NAME, DISP_COLUMN_PROFESSION_OR_SQUAD, DISP_COLUMN_LABORS, @@ -414,6 +425,7 @@ protected: bool do_refresh_names; int first_row, sel_row, num_rows; int first_column, sel_column; + int last_selection; int col_widths[DISP_COLUMN_MAX]; int col_offsets[DISP_COLUMN_MAX]; @@ -443,6 +455,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur cur->unit = unit; cur->allowEdit = true; + cur->selected = false; cur->active_index = active_idx[unit]; if (!Units::isOwnRace(unit)) @@ -481,6 +494,8 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur // don't scroll beyond the end if (first_row > units.size() - num_rows) first_row = units.size() - num_rows; + + last_selection = -1; } void viewscreen_unitlaborsst::refreshNames() @@ -521,6 +536,8 @@ void viewscreen_unitlaborsst::calcSize() int col_maxwidth[DISP_COLUMN_MAX]; col_minwidth[DISP_COLUMN_STRESS] = 6; col_maxwidth[DISP_COLUMN_STRESS] = 6; + col_minwidth[DISP_COLUMN_SELECTED] = 1; + col_maxwidth[DISP_COLUMN_SELECTED] = 1; col_minwidth[DISP_COLUMN_NAME] = 16; col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below col_minwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10; @@ -799,6 +816,17 @@ void viewscreen_unitlaborsst::feed(set *events) } break; + case DISP_COLUMN_SELECTED: + if (enabler->mouse_lbut || enabler->mouse_rbut) + { + input_sort = ALTSORT_SELECTED; + if (enabler->mouse_lbut) + events->insert(interface_key::SECONDSCROLL_PAGEUP); + if (enabler->mouse_rbut) + events->insert(interface_key::SECONDSCROLL_PAGEDOWN); + } + break; + case DISP_COLUMN_NAME: if (enabler->mouse_lbut || enabler->mouse_rbut) { @@ -839,6 +867,20 @@ void viewscreen_unitlaborsst::feed(set *events) // do nothing break; + case DISP_COLUMN_SELECTED: + // left-click to select, right-click to extend selection + if (enabler->mouse_lbut) + { + input_row = click_unit; + events->insert(interface_key::CUSTOM_X); + } + if (enabler->mouse_rbut) + { + input_row = click_unit; + events->insert(interface_key::CUSTOM_SHIFT_X); + } + break; + case DISP_COLUMN_NAME: case DISP_COLUMN_PROFESSION_OR_SQUAD: // left-click to view, right-click to zoom @@ -938,6 +980,9 @@ void viewscreen_unitlaborsst::feed(set *events) case ALTSORT_NAME: std::stable_sort(units.begin(), units.end(), sortByName); break; + case ALTSORT_SELECTED: + std::stable_sort(units.begin(), units.end(), sortBySelected); + break; case ALTSORT_PROFESSION_OR_SQUAD: std::stable_sort(units.begin(), units.end(), show_squad ? sortBySquad : sortByProfession); break; @@ -954,6 +999,9 @@ void viewscreen_unitlaborsst::feed(set *events) switch (altsort) { case ALTSORT_NAME: + altsort = ALTSORT_SELECTED; + break; + case ALTSORT_SELECTED: altsort = ALTSORT_PROFESSION_OR_SQUAD; break; case ALTSORT_PROFESSION_OR_SQUAD: @@ -972,6 +1020,28 @@ void viewscreen_unitlaborsst::feed(set *events) show_squad = !show_squad; } + if (events->count(interface_key::CUSTOM_SHIFT_X)) + { + if (last_selection == -1 || last_selection == input_row) + events->insert(interface_key::CUSTOM_X); + else + { + for (int i = std::min(input_row, last_selection); + i <= std::max(input_row, last_selection); + i++) + { + if (i == last_selection) continue; + units[i]->selected = units[last_selection]->selected; + } + } + } + + if (events->count(interface_key::CUSTOM_X)) + { + cur->selected = !cur->selected; + last_selection = input_row; + } + if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) { if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE)) @@ -1011,6 +1081,7 @@ void viewscreen_unitlaborsst::render() Screen::drawBorder(" Dwarf Manipulator - Manage Labors "); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_STRESS], 2, "Stress"); + Screen::paintTile(Screen::Pen('\373', 7, 0), col_offsets[DISP_COLUMN_SELECTED], 2); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name"); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION_OR_SQUAD], 2, show_squad ? "Squad" : "Profession"); @@ -1070,6 +1141,11 @@ void viewscreen_unitlaborsst::render() fg = 10; // 2:1 Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_STRESS], 4 + row, stress); + if (cur->selected) + Screen::paintTile(Screen::Pen('\373', 10, 0), col_offsets[DISP_COLUMN_SELECTED], 4 + row); + else + Screen::paintTile(Screen::Pen('-', 8, 0), col_offsets[DISP_COLUMN_SELECTED], 4 + row); + fg = 15; if (row_offset == sel_row) { @@ -1204,7 +1280,7 @@ void viewscreen_unitlaborsst::render() canToggle = (cur->allowEdit) && columns[sel_column].isValidLabor(ui->main.fortress_entity); } - int x = 2, y = dim.y - 3; + int x = 2, y = dim.y - 4; OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT)); OutputString(canToggle ? 15 : 8, x, y, ": Toggle labor, "); @@ -1217,7 +1293,7 @@ void viewscreen_unitlaborsst::render() OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); OutputString(15, x, y, ": Zoom-Cre"); - x = 2; y = dim.y - 2; + x = 2; y = dim.y - 3; OutputString(10, x, y, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); OutputString(15, x, y, ": Done, "); @@ -1238,6 +1314,9 @@ void viewscreen_unitlaborsst::render() case ALTSORT_NAME: OutputString(15, x, y, "Name"); break; + case ALTSORT_SELECTED: + OutputString(15, x, y, "Selected"); + break; case ALTSORT_PROFESSION_OR_SQUAD: OutputString(15, x, y, show_squad ? "Squad" : "Profession"); break; @@ -1251,6 +1330,11 @@ void viewscreen_unitlaborsst::render() OutputString(15, x, y, "Unknown"); break; } + + x = 2; y = dim.y - 2; + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_X)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_SHIFT_X)); + OutputString(15, x, y, ": Select"); } df::unit *viewscreen_unitlaborsst::getSelectedUnit() @@ -1314,6 +1398,11 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { + if (!Filesystem::isdir(CONFIG_PATH) && !Filesystem::mkdir(CONFIG_PATH)) + { + out.printerr("manipulator: Could not create configuration folder: \"%s\"\n", CONFIG_PATH); + return CR_FAILURE; + } return CR_OK; } From 990ab9c76b017c0433c3243fe525dfe64aff2382 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jan 2015 23:01:14 -0500 Subject: [PATCH 02/15] Typecast nullptr in ListColumn::getFirstSelectedElem() --- plugins/uicommon.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/uicommon.h b/plugins/uicommon.h index 82df61d61..c725e52d6 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -719,7 +719,7 @@ public: { vector results = getSelectedElems(true); if (results.size() == 0) - return nullptr; + return (T)nullptr; else return results[0]; } From 9d600f00a0fb65e17afac85d9e995fb1ea9aa7c9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 8 Jan 2015 23:19:06 -0500 Subject: [PATCH 03/15] Add batch operations menu --- plugins/manipulator.cpp | 108 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index e9d918df3..6483039f4 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -30,6 +30,8 @@ #include "df/historical_entity.h" #include "df/entity_raw.h" +#include "uicommon.h" + using std::set; using std::vector; using std::string; @@ -386,6 +388,89 @@ bool sortBySelected (const UnitInfo *d1, const UnitInfo *d2) return descending ? (d1->selected > d2->selected) : (d1->selected < d2->selected); } +class viewscreen_unitbatchopst : public dfhack_viewscreen { +public: + enum page { MENU, NICKNAME, PROFNAME }; + viewscreen_unitbatchopst() + :cur_page(MENU), entry("") + { + menu_options.multiselect = false; + menu_options.auto_select = true; + menu_options.allow_search = false; + menu_options.left_margin = 2; + menu_options.bottom_margin = 2; + menu_options.clear(); + menu_options.add("Change nickname", page::NICKNAME); + menu_options.add("Change profession name", page::PROFNAME); + menu_options.filterDisplay(); + } + void select_page (page p) + { + if (p == NICKNAME || p == PROFNAME) + entry = ""; + cur_page = p; + } + std::string getFocusString() { return "unitlabors/batch"; } + void feed(set *events) + { + if (cur_page == MENU) + { + if (events->count(interface_key::LEAVESCREEN)) + Screen::dismiss(this); + else if (events->count(interface_key::SELECT)) + select_page(menu_options.getFirstSelectedElem()); + else if (menu_options.feed(events)) + return; + } + else if (cur_page == NICKNAME || cur_page == PROFNAME) + { + if (events->count(interface_key::LEAVESCREEN)) + select_page(MENU); + else + { + for (auto it = events->begin(); it != events->end(); ++it) + { + int ch = Screen::keyToChar(*it); + if (ch == 0 && entry.size()) + entry.resize(entry.size() - 1); + else if (ch > 0) + entry.push_back(char(ch)); + } + } + } + } + void render() + { + dfhack_viewscreen::render(); + Screen::clear(); + if (cur_page == MENU) + { + Screen::drawBorder(" Dwarf Manipulator - Batch Operations "); + menu_options.display(true); + } + else if (cur_page == NICKNAME || cur_page == PROFNAME) + { + std::string name_type = (cur_page == page::NICKNAME) ? "Nickname" : "Profession name"; + int x = 2, y = 2; + OutputString(COLOR_GREY, x, y, "Custom " + name_type + ":"); + x = 2; y = 4; + OutputString(COLOR_WHITE, x, y, entry); + OutputString(COLOR_LIGHTGREEN, x, y, "_"); + x = 2; y = 6; + } + } +protected: + ListColumn menu_options; + page cur_page; + std::string entry; +private: + void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + menu_options.resize(); + } +}; + enum display_columns { DISP_COLUMN_STRESS, DISP_COLUMN_SELECTED, @@ -1042,6 +1127,17 @@ void viewscreen_unitlaborsst::feed(set *events) last_selection = input_row; } + if (events->count(interface_key::CUSTOM_A) || events->count(interface_key::CUSTOM_SHIFT_A)) + { + for (size_t i = 0; i < units.size(); i++) + units[i]->selected = (bool)events->count(interface_key::CUSTOM_A); + } + + if (events->count(interface_key::CUSTOM_B)) + { + Screen::show(new viewscreen_unitbatchopst); + } + if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) { if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE)) @@ -1063,11 +1159,6 @@ void viewscreen_unitlaborsst::feed(set *events) } } -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(); -} void viewscreen_unitlaborsst::render() { if (Screen::isDismissed(this)) @@ -1334,7 +1425,12 @@ void viewscreen_unitlaborsst::render() x = 2; y = dim.y - 2; OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_X)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_SHIFT_X)); - OutputString(15, x, y, ": Select"); + OutputString(15, x, y, ": Select "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_A)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_SHIFT_A)); + OutputString(15, x, y, ": all/none, "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_B)); + OutputString(15, x, y, ": Batch, "); } df::unit *viewscreen_unitlaborsst::getSelectedUnit() From dd17f90dcd1e3afe443e80dadb1a82f4a5d81196 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Jan 2015 13:22:27 -0500 Subject: [PATCH 04/15] Add a flag to allow mouse clicks to select ListColumn items --- plugins/uicommon.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/uicommon.h b/plugins/uicommon.h index c725e52d6..56519c260 100644 --- a/plugins/uicommon.h +++ b/plugins/uicommon.h @@ -451,6 +451,7 @@ public: bool allow_null; bool auto_select; bool allow_search; + bool feed_mouse_set_highlight; bool feed_changed_highlight; ListColumn() @@ -465,6 +466,7 @@ public: allow_null = true; auto_select = false; allow_search = true; + feed_mouse_set_highlight = false; feed_changed_highlight = false; } @@ -765,7 +767,7 @@ public: bool feed(set *input) { - feed_changed_highlight = false; + feed_mouse_set_highlight = feed_changed_highlight = false; if (input->count(interface_key::CURSOR_UP)) { changeHighlight(-1); @@ -838,7 +840,10 @@ public: { int new_index = display_start_offset + gps->mouse_y - 3; if (new_index < display_list.size()) + { setHighlight(new_index); + feed_mouse_set_highlight = true; + } enabler->mouse_lbut = enabler->mouse_rbut = 0; From 21e96ba3699712d77525f03e76565942228020ef Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Jan 2015 13:24:03 -0500 Subject: [PATCH 05/15] Implement batch operation callbacks and a basic string formatter --- plugins/manipulator.cpp | 93 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 6483039f4..a4fadc5a9 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -388,11 +388,46 @@ bool sortBySelected (const UnitInfo *d1, const UnitInfo *d2) return descending ? (d1->selected > d2->selected) : (d1->selected < d2->selected); } +template +class StringFormatter { +public: + typedef std::tuple T_opt; + typedef vector T_optlist; + StringFormatter() {} + void add_option(string spec, string help, string (*callback)(T)) + { + format_options.push_back(std::make_tuple(spec, help, callback)); + } + T_optlist *get_options() { return &format_options; } + void clear_options() { format_options.clear(); } + string format (T obj, string fmt) + { + return fmt; + } +protected: + T_optlist format_options; +}; + +namespace unit_ops { + std::string get_real_name(df::unit *u) + { return Translation::TranslateName(&u->name); } + std::string get_nickname(df::unit *u) + { return Translation::TranslateName(Units::getVisibleName(u)); } + std::string get_real_name_eng(df::unit *u) + { return Translation::TranslateName(&u->name, true); } + std::string get_nickname_eng(df::unit *u) + { return Translation::TranslateName(Units::getVisibleName(u), true); } + void set_nickname(df::unit *u, std::string nick) + { Units::setNickname(u, nick); } + void set_profname(df::unit *u, std::string prof) + { u->custom_profession = prof; } +} + class viewscreen_unitbatchopst : public dfhack_viewscreen { public: enum page { MENU, NICKNAME, PROFNAME }; - viewscreen_unitbatchopst() - :cur_page(MENU), entry("") + viewscreen_unitbatchopst(vector *units) + :cur_page(MENU), entry(""), units(units) { menu_options.multiselect = false; menu_options.auto_select = true; @@ -403,29 +438,61 @@ public: menu_options.add("Change nickname", page::NICKNAME); menu_options.add("Change profession name", page::PROFNAME); menu_options.filterDisplay(); + formatter.add_option("n", "Displayed name (or nickname)", unit_ops::get_nickname); + formatter.add_option("N", "Real name", unit_ops::get_real_name); + formatter.add_option("en", "Displayed name (or nickname), English", unit_ops::get_nickname_eng); + formatter.add_option("eN", "Real name, English", unit_ops::get_real_name_eng); } + std::string getFocusString() { return "unitlabors/batch"; } void select_page (page p) { if (p == NICKNAME || p == PROFNAME) entry = ""; cur_page = p; } - std::string getFocusString() { return "unitlabors/batch"; } + string format_string(df::unit* u, string format) + { + return format; + } + void apply(void (*func)(df::unit*, string), string arg, StringFormatter *arg_formatter) + { + for (auto it = units->begin(); it != units->end(); ++it) + { + df::unit* u = (*it)->unit; + if (!u) continue; + string cur_arg = arg_formatter->format(u, arg); + func(u, cur_arg); + } + } void feed(set *events) { if (cur_page == MENU) { if (events->count(interface_key::LEAVESCREEN)) + { Screen::dismiss(this); - else if (events->count(interface_key::SELECT)) - select_page(menu_options.getFirstSelectedElem()); - else if (menu_options.feed(events)) return; + } + if (menu_options.feed(events)) + { + // Allow left mouse button to trigger menu options + if (menu_options.feed_mouse_set_highlight) + events->insert(interface_key::SELECT); + else + return; + } + if (events->count(interface_key::SELECT)) + select_page(menu_options.getFirstSelectedElem()); } else if (cur_page == NICKNAME || cur_page == PROFNAME) { if (events->count(interface_key::LEAVESCREEN)) select_page(MENU); + else if (events->count(interface_key::SELECT)) + { + apply((cur_page == NICKNAME) ? unit_ops::set_nickname : unit_ops::set_profname, entry, &formatter); + select_page(MENU); + } else { for (auto it = events->begin(); it != events->end(); ++it) @@ -457,12 +524,22 @@ public: OutputString(COLOR_WHITE, x, y, entry); OutputString(COLOR_LIGHTGREEN, x, y, "_"); x = 2; y = 6; + StringFormatter::T_optlist *format_options = formatter.get_options(); + for (auto it = format_options->begin(); it != format_options->end(); ++it) + { + auto opt = *it; + OutputString(COLOR_LIGHTCYAN, x, y, "%" + string(std::get<0>(opt))); + OutputString(COLOR_WHITE, x, y, ": " + string(std::get<1>(opt))); + x = 2; y++; + } } } protected: ListColumn menu_options; page cur_page; - std::string entry; + string entry; + vector *units; + StringFormatter formatter; private: void resize(int32_t x, int32_t y) { @@ -1135,7 +1212,7 @@ void viewscreen_unitlaborsst::feed(set *events) if (events->count(interface_key::CUSTOM_B)) { - Screen::show(new viewscreen_unitbatchopst); + Screen::show(new viewscreen_unitbatchopst(&units)); } if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) From b5797daa8a0d0113df089a75f97e951781190d37 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Jan 2015 15:05:49 -0500 Subject: [PATCH 06/15] Allow custom nicknames/profession names to use format specifiers Also prevent selecting/applying operations to uneditable units --- plugins/manipulator.cpp | 192 +++++++++++++++++++++++++++++++--------- 1 file changed, 152 insertions(+), 40 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index a4fadc5a9..f6a6359c6 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -32,6 +32,7 @@ #include "uicommon.h" +using std::stringstream; using std::set; using std::vector; using std::string; @@ -287,6 +288,13 @@ enum altsort_mode { ALTSORT_MAX }; +string itos (int n) +{ + stringstream ss; + ss << n; + return ss.str(); +} + bool descending; df::job_skill sort_skill; df::unit_labor sort_labor; @@ -391,43 +399,130 @@ bool sortBySelected (const UnitInfo *d1, const UnitInfo *d2) template class StringFormatter { public: - typedef std::tuple T_opt; + typedef string(*T_callback)(T); + typedef std::tuple T_opt; typedef vector T_optlist; + static bool compare_opts(const T_opt &first, const T_opt &second) + { + // Sort by option length, decreasing + return std::get<0>(first).size() > std::get<0>(second).size(); + } StringFormatter() {} void add_option(string spec, string help, string (*callback)(T)) { - format_options.push_back(std::make_tuple(spec, help, callback)); + opt_list.push_back(std::make_tuple(spec, help, callback)); + std::sort(opt_list.begin(), opt_list.end(), StringFormatter::compare_opts); + } + T_optlist *get_options() { return &opt_list; } + void clear_options() + { + opt_list.clear(); + } + string grab_opt (string s, size_t start) + { + for (auto it = opt_list.begin(); it != opt_list.end(); ++it) + { + string opt = std::get<0>(*it); + if (opt == s.substr(start, opt.size())) + return opt; + } + return ""; + } + T_callback get_callback (string s) + { + for (auto it = opt_list.begin(); it != opt_list.end(); ++it) + { + if (std::get<0>(*it) == s) + return std::get<2>(*it); + } + return NULL; } - T_optlist *get_options() { return &format_options; } - void clear_options() { format_options.clear(); } string format (T obj, string fmt) { - return fmt; + string dest = ""; + bool in_opt = false; + size_t i = 0; + while (i < fmt.size()) + { + if (in_opt) + { + if (fmt[i] == '%') + { + // escape: %% -> % + in_opt = false; + dest.push_back('%'); + i++; + } + else + { + string opt = grab_opt(fmt, i); + if (opt.size()) + { + T_callback func = get_callback(opt); + if (func != NULL) + dest += func(obj); + i += opt.size(); + } + else + { + // Unrecognized format option; replace with original text + dest.push_back('%'); + in_opt = false; + } + } + } + else + { + if (fmt[i] == '%') + in_opt = true; + else + dest.push_back(fmt[i]); + i++; + } + } + return dest; } protected: - T_optlist format_options; + T_optlist opt_list; }; namespace unit_ops { - std::string get_real_name(df::unit *u) - { return Translation::TranslateName(&u->name); } - std::string get_nickname(df::unit *u) - { return Translation::TranslateName(Units::getVisibleName(u)); } - std::string get_real_name_eng(df::unit *u) - { return Translation::TranslateName(&u->name, true); } - std::string get_nickname_eng(df::unit *u) - { return Translation::TranslateName(Units::getVisibleName(u), true); } - void set_nickname(df::unit *u, std::string nick) - { Units::setNickname(u, nick); } - void set_profname(df::unit *u, std::string prof) - { u->custom_profession = prof; } + string get_real_name(UnitInfo *u) + { return Translation::TranslateName(&u->unit->name, false); } + string get_nickname(UnitInfo *u) + { return Translation::TranslateName(Units::getVisibleName(u->unit), false); } + string get_real_name_eng(UnitInfo *u) + { return Translation::TranslateName(&u->unit->name, true); } + string get_nickname_eng(UnitInfo *u) + { return Translation::TranslateName(Units::getVisibleName(u->unit), true); } + string get_profname(UnitInfo *u) + { return Units::getProfessionName(u->unit); } + string get_real_profname(UnitInfo *u) + { + string tmp = u->unit->custom_profession; + u->unit->custom_profession = ""; + string ret = get_profname(u); + u->unit->custom_profession = tmp; + return ret; + } + void set_nickname(UnitInfo *u, std::string nick) + { + Units::setNickname(u->unit, nick); + u->name = get_nickname(u); + u->transname = get_nickname_eng(u); + } + void set_profname(UnitInfo *u, std::string prof) + { + u->unit->custom_profession = prof; + u->profession = get_profname(u); + } } class viewscreen_unitbatchopst : public dfhack_viewscreen { public: enum page { MENU, NICKNAME, PROFNAME }; viewscreen_unitbatchopst(vector *units) - :cur_page(MENU), entry(""), units(units) + :cur_page(MENU), entry(""), units(units), selection_empty(false) { menu_options.multiselect = false; menu_options.auto_select = true; @@ -440,8 +535,16 @@ public: menu_options.filterDisplay(); formatter.add_option("n", "Displayed name (or nickname)", unit_ops::get_nickname); formatter.add_option("N", "Real name", unit_ops::get_real_name); - formatter.add_option("en", "Displayed name (or nickname), English", unit_ops::get_nickname_eng); - formatter.add_option("eN", "Real name, English", unit_ops::get_real_name_eng); + formatter.add_option("en", "Displayed name (or nickname), in English", unit_ops::get_nickname_eng); + formatter.add_option("eN", "Real name, in English", unit_ops::get_real_name_eng); + formatter.add_option("p", "Displayed profession", unit_ops::get_profname); + formatter.add_option("P", "Real profession", unit_ops::get_real_profname); + selection_empty = true; + for (auto it = units->begin(); it != units->end(); ++it) + { + if ((*it)->selected) + selection_empty = false; + } } std::string getFocusString() { return "unitlabors/batch"; } void select_page (page p) @@ -450,16 +553,12 @@ public: entry = ""; cur_page = p; } - string format_string(df::unit* u, string format) - { - return format; - } - void apply(void (*func)(df::unit*, string), string arg, StringFormatter *arg_formatter) + void apply(void (*func)(UnitInfo*, string), string arg, StringFormatter *arg_formatter) { for (auto it = units->begin(); it != units->end(); ++it) { - df::unit* u = (*it)->unit; - if (!u) continue; + UnitInfo* u = (*it); + if (!u || !u->unit || !u->selected || !u->allowEdit) continue; string cur_arg = arg_formatter->format(u, arg); func(u, cur_arg); } @@ -473,6 +572,8 @@ public: Screen::dismiss(this); return; } + if (selection_empty) + return; if (menu_options.feed(events)) { // Allow left mouse button to trigger menu options @@ -510,27 +611,35 @@ public: { dfhack_viewscreen::render(); Screen::clear(); + int x = 2, y = 2; if (cur_page == MENU) { Screen::drawBorder(" Dwarf Manipulator - Batch Operations "); + if (selection_empty) + { + OutputString(COLOR_LIGHTRED, x, y, "No dwarves selected!"); + return; + } menu_options.display(true); } else if (cur_page == NICKNAME || cur_page == PROFNAME) { std::string name_type = (cur_page == page::NICKNAME) ? "Nickname" : "Profession name"; - int x = 2, y = 2; OutputString(COLOR_GREY, x, y, "Custom " + name_type + ":"); - x = 2; y = 4; + x = 2; y += 2; OutputString(COLOR_WHITE, x, y, entry); OutputString(COLOR_LIGHTGREEN, x, y, "_"); - x = 2; y = 6; - StringFormatter::T_optlist *format_options = formatter.get_options(); + x = 2; y += 2; + OutputString(COLOR_DARKGREY, x, y, "(Leave blank to use original name)"); + x = 2; y += 2; + OutputString(COLOR_WHITE, x, y, "Format options:"); + StringFormatter::T_optlist *format_options = formatter.get_options(); for (auto it = format_options->begin(); it != format_options->end(); ++it) { + x = 2; y++; auto opt = *it; OutputString(COLOR_LIGHTCYAN, x, y, "%" + string(std::get<0>(opt))); OutputString(COLOR_WHITE, x, y, ": " + string(std::get<1>(opt))); - x = 2; y++; } } } @@ -539,7 +648,8 @@ protected: page cur_page; string entry; vector *units; - StringFormatter formatter; + StringFormatter formatter; + bool selection_empty; private: void resize(int32_t x, int32_t y) { @@ -1193,12 +1303,13 @@ void viewscreen_unitlaborsst::feed(set *events) i++) { if (i == last_selection) continue; + if (!units[i]->allowEdit) continue; units[i]->selected = units[last_selection]->selected; } } } - if (events->count(interface_key::CUSTOM_X)) + if (events->count(interface_key::CUSTOM_X) && cur->allowEdit) { cur->selected = !cur->selected; last_selection = input_row; @@ -1207,7 +1318,8 @@ void viewscreen_unitlaborsst::feed(set *events) if (events->count(interface_key::CUSTOM_A) || events->count(interface_key::CUSTOM_SHIFT_A)) { for (size_t i = 0; i < units.size(); i++) - units[i]->selected = (bool)events->count(interface_key::CUSTOM_A); + if (units[i]->allowEdit) + units[i]->selected = (bool)events->count(interface_key::CUSTOM_A); } if (events->count(interface_key::CUSTOM_B)) @@ -1309,10 +1421,10 @@ void viewscreen_unitlaborsst::render() fg = 10; // 2:1 Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_STRESS], 4 + row, stress); - if (cur->selected) - Screen::paintTile(Screen::Pen('\373', 10, 0), col_offsets[DISP_COLUMN_SELECTED], 4 + row); - else - Screen::paintTile(Screen::Pen('-', 8, 0), col_offsets[DISP_COLUMN_SELECTED], 4 + row); + Screen::paintTile( + (cur->selected) ? Screen::Pen('\373', COLOR_LIGHTGREEN, 0) : + ((cur->allowEdit) ? Screen::Pen('-', COLOR_DARKGREY, 0) : Screen::Pen('-', COLOR_RED, 0)), + col_offsets[DISP_COLUMN_SELECTED], 4 + row); fg = 15; if (row_offset == sel_row) From 5a92080cc1fb1321fc06c56bd7146285583f39eb Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Jan 2015 17:15:14 -0500 Subject: [PATCH 07/15] Add various ID formatting options, allow editing a single dwarf --- plugins/manipulator.cpp | 117 +++++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 18 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index f6a6359c6..409411e30 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -277,6 +277,17 @@ struct UnitInfo string squad_effective_name; string squad_info; bool selected; + struct { + // Used for custom professions, 1-indexed + int list_id; // Position in list + int list_id_group; // Position in list by group (e.g. craftsdwarf) + int list_id_prof; // Position in list by profession (e.g. woodcrafter) + void init() { + list_id = 0; + list_id_group = 0; + list_id_prof = 0; + } + } ids; }; enum altsort_mode { @@ -402,16 +413,14 @@ public: typedef string(*T_callback)(T); typedef std::tuple T_opt; typedef vector T_optlist; - static bool compare_opts(const T_opt &first, const T_opt &second) + static bool compare_opts(const string &first, const string &second) { - // Sort by option length, decreasing - return std::get<0>(first).size() > std::get<0>(second).size(); + return first.size() > second.size(); } StringFormatter() {} void add_option(string spec, string help, string (*callback)(T)) { opt_list.push_back(std::make_tuple(spec, help, callback)); - std::sort(opt_list.begin(), opt_list.end(), StringFormatter::compare_opts); } T_optlist *get_options() { return &opt_list; } void clear_options() @@ -420,13 +429,18 @@ public: } string grab_opt (string s, size_t start) { + vector candidates; for (auto it = opt_list.begin(); it != opt_list.end(); ++it) { string opt = std::get<0>(*it); - if (opt == s.substr(start, opt.size())) - return opt; + string slice = s.substr(start, opt.size()); + if (slice == opt) + candidates.push_back(slice); } - return ""; + if (!candidates.size()) + return ""; + std::sort(candidates.begin(), candidates.end(), StringFormatter::compare_opts); + return candidates[0]; } T_callback get_callback (string s) { @@ -462,6 +476,9 @@ public: if (func != NULL) dest += func(obj); i += opt.size(); + if (i < opt.size() && opt[i] == '$') + // Allow $ to terminate format options + i++; } else { @@ -505,6 +522,15 @@ namespace unit_ops { u->unit->custom_profession = tmp; return ret; } + #define id_getter(id) \ + string get_##id(UnitInfo *u) \ + { return itos(u->ids.id); } + id_getter(list_id); + id_getter(list_id_prof); + id_getter(list_id_group); + #undef id_getter + string get_unit_id(UnitInfo *u) + { return itos(u->unit->id); } void set_nickname(UnitInfo *u, std::string nick) { Units::setNickname(u->unit, nick); @@ -521,8 +547,8 @@ namespace unit_ops { class viewscreen_unitbatchopst : public dfhack_viewscreen { public: enum page { MENU, NICKNAME, PROFNAME }; - viewscreen_unitbatchopst(vector *units) - :cur_page(MENU), entry(""), units(units), selection_empty(false) + viewscreen_unitbatchopst(vector &base_units, bool filter_selected = true) + :cur_page(MENU), entry(""), selection_empty(false) { menu_options.multiselect = false; menu_options.auto_select = true; @@ -539,11 +565,19 @@ public: formatter.add_option("eN", "Real name, in English", unit_ops::get_real_name_eng); formatter.add_option("p", "Displayed profession", unit_ops::get_profname); formatter.add_option("P", "Real profession", unit_ops::get_real_profname); + formatter.add_option("i", "Position in list", unit_ops::get_list_id); + formatter.add_option("pi", "Position in list, among dwarves with same profession", unit_ops::get_list_id_prof); + formatter.add_option("gi", "Position in list, among dwarves in same profession group", unit_ops::get_list_id_group); + formatter.add_option("ri", "Raw unit ID", unit_ops::get_unit_id); selection_empty = true; - for (auto it = units->begin(); it != units->end(); ++it) + for (auto it = base_units.begin(); it != base_units.end(); ++it) { - if ((*it)->selected) + UnitInfo* uinfo = *it; + if (uinfo->selected || !filter_selected) + { selection_empty = false; + units.push_back(uinfo); + } } } std::string getFocusString() { return "unitlabors/batch"; } @@ -555,10 +589,10 @@ public: } void apply(void (*func)(UnitInfo*, string), string arg, StringFormatter *arg_formatter) { - for (auto it = units->begin(); it != units->end(); ++it) + for (auto it = units.begin(); it != units.end(); ++it) { UnitInfo* u = (*it); - if (!u || !u->unit || !u->selected || !u->allowEdit) continue; + if (!u || !u->unit || !u->allowEdit) continue; string cur_arg = arg_formatter->format(u, arg); func(u, cur_arg); } @@ -622,11 +656,32 @@ public: } menu_options.display(true); } - else if (cur_page == NICKNAME || cur_page == PROFNAME) + OutputString(COLOR_LIGHTGREEN, x, y, itos(units.size())); + OutputString(COLOR_GREY, x, y, string(" ") + (units.size() > 1 ? "dwarves" : "dwarf") + " selected: "); + int max_x = gps->dimx - 2; + size_t i = 0; + for ( ; i < units.size(); i++) + { + string name = unit_ops::get_nickname(units[i]); + if (name.size() + x + 12 >= max_x) // 12 = "and xxx more" + break; + OutputString(COLOR_WHITE, x, y, name + ", "); + } + if (i == units.size()) + { + x -= 2; + OutputString(COLOR_WHITE, x, y, " "); + } + else + { + OutputString(COLOR_GREY, x, y, "and " + itos(units.size() - i) + " more"); + } + x = 2; y += 2; + if (cur_page == NICKNAME || cur_page == PROFNAME) { std::string name_type = (cur_page == page::NICKNAME) ? "Nickname" : "Profession name"; OutputString(COLOR_GREY, x, y, "Custom " + name_type + ":"); - x = 2; y += 2; + x = 2; y += 1; OutputString(COLOR_WHITE, x, y, entry); OutputString(COLOR_LIGHTGREEN, x, y, "_"); x = 2; y += 2; @@ -647,7 +702,7 @@ protected: ListColumn menu_options; page cur_page; string entry; - vector *units; + vector units; StringFormatter formatter; bool selection_empty; private: @@ -703,6 +758,7 @@ protected: int col_offsets[DISP_COLUMN_MAX]; void refreshNames(); + void calcIDs(); void calcSize (); }; @@ -725,6 +781,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur UnitInfo *cur = new UnitInfo; + cur->ids.init(); cur->unit = unit; cur->allowEdit = true; cur->selected = false; @@ -751,6 +808,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur first_column = sel_column = 0; refreshNames(); + calcIDs(); first_row = 0; sel_row = cursor_pos; @@ -770,6 +828,20 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur last_selection = -1; } +void viewscreen_unitlaborsst::calcIDs() +{ + static int list_prof_ids[NUM_COLUMNS]; + static int list_group_ids[NUM_COLUMNS]; + memset(list_prof_ids, 0, sizeof(list_prof_ids)); + memset(list_group_ids, 0, sizeof(list_group_ids)); + for (size_t i = 0; i < units.size(); i++) + { + UnitInfo *cur = units[i]; + cur->ids.list_id = (int)i + 1; + cur->ids.list_id_prof = ++list_prof_ids[cur->unit->profession]; + } +} + void viewscreen_unitlaborsst::refreshNames() { do_refresh_names = false; @@ -1324,7 +1396,14 @@ void viewscreen_unitlaborsst::feed(set *events) if (events->count(interface_key::CUSTOM_B)) { - Screen::show(new viewscreen_unitbatchopst(&units)); + Screen::show(new viewscreen_unitbatchopst(units, true)); + } + + if (events->count(interface_key::CUSTOM_E)) + { + vector tmp; + tmp.push_back(cur); + Screen::show(new viewscreen_unitbatchopst(tmp, false)); } if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) @@ -1619,7 +1698,9 @@ void viewscreen_unitlaborsst::render() OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_SHIFT_A)); OutputString(15, x, y, ": all/none, "); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_B)); - OutputString(15, x, y, ": Batch, "); + OutputString(15, x, y, ": Batch "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_E)); + OutputString(15, x, y, ": Edit "); } df::unit *viewscreen_unitlaborsst::getSelectedUnit() From c36daa4d7da13b2846e37fe4ed1a3c12c73abcc0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Jan 2015 20:02:44 -0500 Subject: [PATCH 08/15] Fix a few minor StringFormatter issues * Recognize the end of a format specifier properly * Make '$' actually work --- plugins/manipulator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 409411e30..d1bbdd8f1 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -439,6 +439,7 @@ public: } if (!candidates.size()) return ""; + // Select the longest candidate std::sort(candidates.begin(), candidates.end(), StringFormatter::compare_opts); return candidates[0]; } @@ -476,7 +477,8 @@ public: if (func != NULL) dest += func(obj); i += opt.size(); - if (i < opt.size() && opt[i] == '$') + in_opt = false; + if (i < fmt.size() && fmt[i] == '$') // Allow $ to terminate format options i++; } From e2d7a7395ece9729595b99f1d186de7ce033c4c1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 9 Jan 2015 20:28:05 -0500 Subject: [PATCH 09/15] Add several additional format options Also set do_refresh_names and call calcIDs() when needed --- plugins/manipulator.cpp | 81 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index d1bbdd8f1..15cc57022 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -514,6 +514,35 @@ namespace unit_ops { { return Translation::TranslateName(&u->unit->name, true); } string get_nickname_eng(UnitInfo *u) { return Translation::TranslateName(Units::getVisibleName(u->unit), true); } + string get_first_nickname(UnitInfo *u) + { + return Translation::capitalize(u->unit->name.nickname.size() ? + u->unit->name.nickname : u->unit->name.first_name); + } + string get_first_name(UnitInfo *u) + { return Translation::capitalize(u->unit->name.first_name); } + string get_last_name(UnitInfo *u) + { + df::language_name name = u->unit->name; + string ret = ""; + for (int i = 0; i < 2; i++) + { + if (name.words[i] >= 0) + ret += *world->raws.language.translations[name.language]->words[name.words[i]]; + } + return Translation::capitalize(ret); + } + string get_last_name_eng(UnitInfo *u) + { + df::language_name name = u->unit->name; + string ret = ""; + for (int i = 0; i < 2; i++) + { + if (name.words[i] >= 0) + ret += world->raws.language.words[name.words[i]]->forms[name.parts_of_speech[i].value]; + } + return Translation::capitalize(ret); + } string get_profname(UnitInfo *u) { return Units::getProfessionName(u->unit); } string get_real_profname(UnitInfo *u) @@ -524,6 +553,19 @@ namespace unit_ops { u->unit->custom_profession = tmp; return ret; } + string get_base_profname(UnitInfo *u) + { + return ENUM_ATTR_STR(profession, caption, u->unit->profession); + } + string get_short_profname(UnitInfo *u) + { + for (int i = 0; i < NUM_COLUMNS; i++) + { + if (columns[i].profession == u->unit->profession) + return string(columns[i].label); + } + return "??"; + } #define id_getter(id) \ string get_##id(UnitInfo *u) \ { return itos(u->ids.id); } @@ -533,6 +575,8 @@ namespace unit_ops { #undef id_getter string get_unit_id(UnitInfo *u) { return itos(u->unit->id); } + string get_age(UnitInfo *u) + { return itos((int)Units::getAge(u->unit)); } void set_nickname(UnitInfo *u, std::string nick) { Units::setNickname(u->unit, nick); @@ -549,8 +593,11 @@ namespace unit_ops { class viewscreen_unitbatchopst : public dfhack_viewscreen { public: enum page { MENU, NICKNAME, PROFNAME }; - viewscreen_unitbatchopst(vector &base_units, bool filter_selected = true) - :cur_page(MENU), entry(""), selection_empty(false) + viewscreen_unitbatchopst(vector &base_units, + bool filter_selected = true, + bool *dirty_flag = NULL + ) + :cur_page(MENU), entry(""), selection_empty(false), dirty(dirty_flag) { menu_options.multiselect = false; menu_options.auto_select = true; @@ -565,8 +612,15 @@ public: formatter.add_option("N", "Real name", unit_ops::get_real_name); formatter.add_option("en", "Displayed name (or nickname), in English", unit_ops::get_nickname_eng); formatter.add_option("eN", "Real name, in English", unit_ops::get_real_name_eng); + formatter.add_option("fn", "Displayed first name (or nickname)", unit_ops::get_first_nickname); + formatter.add_option("fN", "Real first name", unit_ops::get_first_name); + formatter.add_option("ln", "Last name", unit_ops::get_last_name); + formatter.add_option("eln", "Last name, in English", unit_ops::get_last_name_eng); formatter.add_option("p", "Displayed profession", unit_ops::get_profname); - formatter.add_option("P", "Real profession", unit_ops::get_real_profname); + formatter.add_option("P", "Real profession (non-customized)", unit_ops::get_real_profname); + formatter.add_option("bp", "Base profession (excluding nobles & other positions)", unit_ops::get_base_profname); + formatter.add_option("sp", "Short (base) profession name (from manipulator headers)", unit_ops::get_short_profname); + formatter.add_option("a", "Age (in years)", unit_ops::get_age); formatter.add_option("i", "Position in list", unit_ops::get_list_id); formatter.add_option("pi", "Position in list, among dwarves with same profession", unit_ops::get_list_id_prof); formatter.add_option("gi", "Position in list, among dwarves in same profession group", unit_ops::get_list_id_group); @@ -591,6 +645,8 @@ public: } void apply(void (*func)(UnitInfo*, string), string arg, StringFormatter *arg_formatter) { + if (dirty) + *dirty = true; for (auto it = units.begin(); it != units.end(); ++it) { UnitInfo* u = (*it); @@ -707,6 +763,7 @@ protected: vector units; StringFormatter formatter; bool selection_empty; + bool *dirty; private: void resize(int32_t x, int32_t y) { @@ -834,6 +891,14 @@ void viewscreen_unitlaborsst::calcIDs() { static int list_prof_ids[NUM_COLUMNS]; static int list_group_ids[NUM_COLUMNS]; + static map group_map; + static bool initialized = false; + if (!initialized) + { + initialized = true; + for (int i = 0; i < NUM_COLUMNS; i++) + group_map.insert(std::pair(columns[i].profession, columns[i].group)); + } memset(list_prof_ids, 0, sizeof(list_prof_ids)); memset(list_group_ids, 0, sizeof(list_group_ids)); for (size_t i = 0; i < units.size(); i++) @@ -841,6 +906,10 @@ void viewscreen_unitlaborsst::calcIDs() UnitInfo *cur = units[i]; cur->ids.list_id = (int)i + 1; cur->ids.list_id_prof = ++list_prof_ids[cur->unit->profession]; + cur->ids.list_id_group = 0; + auto it = group_map.find(cur->unit->profession); + if (it != group_map.end()) + cur->ids.list_id_group = ++list_group_ids[it->second]; } } @@ -1316,6 +1385,7 @@ void viewscreen_unitlaborsst::feed(set *events) sort_skill = columns[input_column].skill; sort_labor = columns[input_column].labor; std::stable_sort(units.begin(), units.end(), sortBySkill); + calcIDs(); } if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN)) @@ -1339,6 +1409,7 @@ void viewscreen_unitlaborsst::feed(set *events) std::stable_sort(units.begin(), units.end(), sortByArrival); break; } + calcIDs(); } if (events->count(interface_key::CHANGETAB)) { @@ -1398,14 +1469,14 @@ void viewscreen_unitlaborsst::feed(set *events) if (events->count(interface_key::CUSTOM_B)) { - Screen::show(new viewscreen_unitbatchopst(units, true)); + Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names)); } if (events->count(interface_key::CUSTOM_E)) { vector tmp; tmp.push_back(cur); - Screen::show(new viewscreen_unitbatchopst(tmp, false)); + Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names)); } if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) From 70824ade146ffc41957aa8cdd44df91568d5969e Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 14 Jan 2015 15:06:59 -0500 Subject: [PATCH 10/15] Allow shift to extend selection --- plugins/manipulator.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 15cc57022..489c8f35a 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1071,6 +1071,7 @@ void viewscreen_unitlaborsst::calcSize() void viewscreen_unitlaborsst::feed(set *events) { + int8_t modstate = Core::getInstance().getModstate(); bool leave_all = events->count(interface_key::LEAVESCREEN_ALL); if (leave_all || events->count(interface_key::LEAVESCREEN)) { @@ -1283,16 +1284,16 @@ void viewscreen_unitlaborsst::feed(set *events) break; case DISP_COLUMN_SELECTED: - // left-click to select, right-click to extend selection - if (enabler->mouse_lbut) + // left-click to select, right-click or shift-click to extend selection + if (enabler->mouse_rbut || (enabler->mouse_lbut && (modstate & MOD_SHIFT))) { input_row = click_unit; - events->insert(interface_key::CUSTOM_X); + events->insert(interface_key::CUSTOM_SHIFT_X); } - if (enabler->mouse_rbut) + else if (enabler->mouse_lbut) { input_row = click_unit; - events->insert(interface_key::CUSTOM_SHIFT_X); + events->insert(interface_key::CUSTOM_X); } break; From 1d8b2d8cea8b7d4d9c3d423f439a4c44dfaaae60 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 18 Jan 2015 13:03:42 -0500 Subject: [PATCH 11/15] Expose manipulator columns to Lua --- plugins/CMakeLists.txt | 2 +- plugins/lua/manipulator.lua | 17 +++++++ plugins/manipulator.cpp | 89 ++++++++++++++++++++++++++++++++++++- 3 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 plugins/lua/manipulator.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 063e8a90e..caa03cfba 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -132,7 +132,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) - DFHACK_PLUGIN(manipulator manipulator.cpp) + DFHACK_PLUGIN(manipulator manipulator.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(mode mode.cpp) #DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) diff --git a/plugins/lua/manipulator.lua b/plugins/lua/manipulator.lua new file mode 100644 index 000000000..a6dec63b0 --- /dev/null +++ b/plugins/lua/manipulator.lua @@ -0,0 +1,17 @@ +local _ENV = mkmodule('plugins.manipulator') + +--[[ + + Native functions: + + * isEnabled() + * setEnabled(bool) + * has_column(name) + * add_column(name, allow_column, allow_format, column_header, format_spec, format_desc) + * remove_column(name) + * list_columns() + +--]] + + +return _ENV diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 489c8f35a..b9c6af838 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -407,6 +409,91 @@ bool sortBySelected (const UnitInfo *d1, const UnitInfo *d2) return descending ? (d1->selected > d2->selected) : (d1->selected < d2->selected); } +class ManipulatorColumn { +public: + typedef string(*callback)(df::unit*); + ManipulatorColumn(bool allow_column, bool allow_format, + string column_header = "", + string format_spec = "", + string format_desc = "") + :allow_column(allow_column), allow_format(allow_format), + column_header(column_header), format_spec(format_spec), format_desc(format_desc) + { } + bool allow_column; + bool allow_format; + string column_header; + string format_spec; + string format_desc; +}; + +class ManipulatorColumnList : public std::map { +public: + bool contains(string key) { return find(key) != end(); } + void remove(string key) { if (contains(key)) erase(key); } +}; + +ManipulatorColumnList column_list; + +bool has_column (string id) { return column_list.contains(id); } + +bool add_column (string id, bool allow_column, bool allow_format, + string column_header, string format_spec, string format_desc) +{ + if (!column_list.contains(id)) + { + ManipulatorColumn col(allow_column, allow_format, column_header, format_spec, format_desc); + column_list.insert(std::pair(id, col)); + return true; + } + return false; +} + +bool remove_column (string id) +{ + if (column_list.contains(id)) + { + column_list.remove(id); + return true; + } + return false; +} + +static int list_columns (lua_State *L) +{ + lua_newtable(L); + int clist_idx = lua_gettop(L); + int i = 1; + for (auto it = column_list.begin(); it != column_list.end(); ++it) + { + lua_newtable(L); + int col_idx = lua_gettop(L); + std::string foo = it->first; + std::string format_spec = it->second.format_spec; + #define set_field(key) Lua::SetField(L, it->second.key, col_idx, #key) + set_field(allow_column); + set_field(allow_format); + set_field(column_header); + set_field(format_spec); + set_field(format_desc); + #undef set_field + lua_rawseti(L, -2, i); + i++; + } + return 1; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(has_column), + DFHACK_LUA_FUNCTION(add_column), + DFHACK_LUA_FUNCTION(remove_column), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(list_columns), + DFHACK_LUA_END +}; + template class StringFormatter { public: @@ -1285,7 +1372,7 @@ void viewscreen_unitlaborsst::feed(set *events) case DISP_COLUMN_SELECTED: // left-click to select, right-click or shift-click to extend selection - if (enabler->mouse_rbut || (enabler->mouse_lbut && (modstate & MOD_SHIFT))) + if (enabler->mouse_rbut || (enabler->mouse_lbut && (modstate & DFH_MOD_SHIFT))) { input_row = click_unit; events->insert(interface_key::CUSTOM_SHIFT_X); From c2c8ae6a44fc1345a3bb13c12f635a4f7039a7d4 Mon Sep 17 00:00:00 2001 From: James Logsdon Date: Tue, 17 Feb 2015 17:17:26 -0500 Subject: [PATCH 12/15] Add current job as a view mode in manipulator --- Contributors.rst | 1 + NEWS | 1 + plugins/manipulator.cpp | 120 ++++++++++++++++++++++++++++++---------- 3 files changed, 94 insertions(+), 28 deletions(-) diff --git a/Contributors.rst b/Contributors.rst index 591b9f173..c566afc92 100644 --- a/Contributors.rst +++ b/Contributors.rst @@ -75,6 +75,7 @@ Ben Lubar BenLubar miffedmap miffedmap scamtank scamtank Mason11987 Mason11987 +jlogsdon jlogsdon ======================= ==================== =========================== And these are the cool people who made **Stonesense**. diff --git a/NEWS b/NEWS index cf1883735..500217946 100644 --- a/NEWS +++ b/NEWS @@ -97,6 +97,7 @@ DFHack 0.40.24-r0 New Tweaks Misc Improvements added support for searching more lists + manipulator: current job as a view mode (in addition to profession and squad) DFHack 0.40.23-r1 Internals diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index b9c6af838..faa36ca19 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -278,6 +279,7 @@ struct UnitInfo int active_index; string squad_effective_name; string squad_info; + string job_info; bool selected; struct { // Used for custom professions, 1-indexed @@ -292,10 +294,15 @@ struct UnitInfo } ids; }; +enum detail_cols { + DETAIL_MODE_PROFESSION, + DETAIL_MODE_SQUAD, + DETAIL_MODE_JOB +}; enum altsort_mode { ALTSORT_NAME, ALTSORT_SELECTED, - ALTSORT_PROFESSION_OR_SQUAD, + ALTSORT_DETAIL, ALTSORT_STRESS, ALTSORT_ARRIVAL, ALTSORT_MAX @@ -344,6 +351,20 @@ bool sortBySquad (const UnitInfo *d1, const UnitInfo *d2) return descending ? gt : !gt; } +bool sortByJob (const UnitInfo *d1, const UnitInfo *d2) +{ + bool gt = false; + + if (d1->job_info == "Idle") + gt = false; + else if (d2->job_info == "Idle") + gt = true; + else + gt = (d1->job_info > d2->job_info); + + return descending ? gt : !gt; +} + bool sortByStress (const UnitInfo *d1, const UnitInfo *d2) { if (!d1->unit->status.current_soul) @@ -863,7 +884,7 @@ enum display_columns { DISP_COLUMN_STRESS, DISP_COLUMN_SELECTED, DISP_COLUMN_NAME, - DISP_COLUMN_PROFESSION_OR_SQUAD, + DISP_COLUMN_DETAIL, DISP_COLUMN_LABORS, DISP_COLUMN_MAX, }; @@ -893,9 +914,9 @@ public: protected: vector units; altsort_mode altsort; - bool show_squad; bool do_refresh_names; + int detail_mode; int first_row, sel_row, num_rows; int first_column, sel_column; int last_selection; @@ -950,7 +971,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur units.push_back(cur); } altsort = ALTSORT_NAME; - show_squad = false; + detail_mode = DETAIL_MODE_PROFESSION; first_column = sel_column = 0; refreshNames(); @@ -1012,6 +1033,12 @@ void viewscreen_unitlaborsst::refreshNames() cur->name = Translation::TranslateName(Units::getVisibleName(unit), false); cur->transname = Translation::TranslateName(Units::getVisibleName(unit), true); cur->profession = Units::getProfessionName(unit); + + if (unit->job.current_job == NULL) { + cur->job_info = "Idle"; + } else { + cur->job_info = DFHack::Job::getName(unit->job.current_job); + } if (unit->military.squad_id > -1) { cur->squad_effective_name = Units::getSquadName(unit); cur->squad_info = stl_sprintf("%i", unit->military.squad_position + 1) + "." + cur->squad_effective_name; @@ -1042,8 +1069,8 @@ void viewscreen_unitlaborsst::calcSize() col_maxwidth[DISP_COLUMN_SELECTED] = 1; col_minwidth[DISP_COLUMN_NAME] = 16; col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below - col_minwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10; - col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10; // adjusted in the loop below + col_minwidth[DISP_COLUMN_DETAIL] = 10; + col_maxwidth[DISP_COLUMN_DETAIL] = 10; // adjusted in the loop below col_minwidth[DISP_COLUMN_LABORS] = 1; col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; @@ -1052,13 +1079,17 @@ void viewscreen_unitlaborsst::calcSize() { if (col_maxwidth[DISP_COLUMN_NAME] < units[i]->name.size()) col_maxwidth[DISP_COLUMN_NAME] = units[i]->name.size(); - if (show_squad) { - if (col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] < units[i]->squad_info.size()) - col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = units[i]->squad_info.size(); + + size_t detail_cmp; + if (detail_mode == DETAIL_MODE_SQUAD) { + detail_cmp = units[i]->squad_info.size(); + } else if (detail_mode == DETAIL_MODE_JOB) { + detail_cmp = units[i]->job_info.size(); } else { - if (col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] < units[i]->profession.size()) - col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = units[i]->profession.size(); + detail_cmp = units[i]->profession.size(); } + if (col_maxwidth[DISP_COLUMN_DETAIL] < detail_cmp) + col_maxwidth[DISP_COLUMN_DETAIL] = detail_cmp; } // check how much room we have @@ -1341,10 +1372,10 @@ void viewscreen_unitlaborsst::feed(set *events) } break; - case DISP_COLUMN_PROFESSION_OR_SQUAD: + case DISP_COLUMN_DETAIL: if (enabler->mouse_lbut || enabler->mouse_rbut) { - input_sort = ALTSORT_PROFESSION_OR_SQUAD; + input_sort = ALTSORT_DETAIL; if (enabler->mouse_lbut) events->insert(interface_key::SECONDSCROLL_PAGEDOWN); if (enabler->mouse_rbut) @@ -1385,7 +1416,7 @@ void viewscreen_unitlaborsst::feed(set *events) break; case DISP_COLUMN_NAME: - case DISP_COLUMN_PROFESSION_OR_SQUAD: + case DISP_COLUMN_DETAIL: // left-click to view, right-click to zoom if (enabler->mouse_lbut) { @@ -1487,8 +1518,14 @@ void viewscreen_unitlaborsst::feed(set *events) case ALTSORT_SELECTED: std::stable_sort(units.begin(), units.end(), sortBySelected); break; - case ALTSORT_PROFESSION_OR_SQUAD: - std::stable_sort(units.begin(), units.end(), show_squad ? sortBySquad : sortByProfession); + case ALTSORT_DETAIL: + if (detail_mode == DETAIL_MODE_SQUAD) { + std::stable_sort(units.begin(), units.end(), sortBySquad); + } else if (detail_mode == DETAIL_MODE_JOB) { + std::stable_sort(units.begin(), units.end(), sortByJob); + } else { + std::stable_sort(units.begin(), units.end(), sortByProfession); + } break; case ALTSORT_STRESS: std::stable_sort(units.begin(), units.end(), sortByStress); @@ -1507,9 +1544,9 @@ void viewscreen_unitlaborsst::feed(set *events) altsort = ALTSORT_SELECTED; break; case ALTSORT_SELECTED: - altsort = ALTSORT_PROFESSION_OR_SQUAD; + altsort = ALTSORT_DETAIL; break; - case ALTSORT_PROFESSION_OR_SQUAD: + case ALTSORT_DETAIL: altsort = ALTSORT_STRESS; break; case ALTSORT_STRESS: @@ -1522,7 +1559,13 @@ void viewscreen_unitlaborsst::feed(set *events) } if (events->count(interface_key::OPTION20)) { - show_squad = !show_squad; + if (detail_mode == DETAIL_MODE_SQUAD) { + detail_mode = DETAIL_MODE_JOB; + } else if (detail_mode == DETAIL_MODE_JOB) { + detail_mode = DETAIL_MODE_PROFESSION; + } else { + detail_mode = DETAIL_MODE_SQUAD; + } } if (events->count(interface_key::CUSTOM_SHIFT_X)) @@ -1603,7 +1646,16 @@ void viewscreen_unitlaborsst::render() Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_STRESS], 2, "Stress"); Screen::paintTile(Screen::Pen('\373', 7, 0), col_offsets[DISP_COLUMN_SELECTED], 2); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name"); - Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION_OR_SQUAD], 2, show_squad ? "Squad" : "Profession"); + + string detail_str; + if (detail_mode == DETAIL_MODE_SQUAD) { + detail_str = "Squad"; + } else if (detail_mode == DETAIL_MODE_JOB) { + detail_str = "Job"; + } else { + detail_str = "Profession"; + } + Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_DETAIL], 2, detail_str); for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++) { @@ -1678,16 +1730,22 @@ void viewscreen_unitlaborsst::render() Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_NAME], 4 + row, name); bg = 0; - string profession_or_squad; - if (show_squad) { + if (detail_mode == DETAIL_MODE_SQUAD) { fg = 11; - profession_or_squad = cur->squad_info; + detail_str = cur->squad_info; + } else if (detail_mode == DETAIL_MODE_JOB) { + detail_str = cur->job_info; + if (detail_str == "Idle") { + fg = 14; + } else { + fg = 10; + } } else { fg = cur->color; - profession_or_squad = cur->profession; + detail_str = cur->profession; } - profession_or_squad.resize(col_widths[DISP_COLUMN_PROFESSION_OR_SQUAD]); - Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_PROFESSION_OR_SQUAD], 4 + row, profession_or_squad); + detail_str.resize(col_widths[DISP_COLUMN_DETAIL]); + Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_DETAIL], 4 + row, detail_str); // Print unit's skills and labor assignments for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++) @@ -1837,8 +1895,14 @@ void viewscreen_unitlaborsst::render() case ALTSORT_SELECTED: OutputString(15, x, y, "Selected"); break; - case ALTSORT_PROFESSION_OR_SQUAD: - OutputString(15, x, y, show_squad ? "Squad" : "Profession"); + case ALTSORT_DETAIL: + if (detail_mode == DETAIL_MODE_SQUAD) { + OutputString(15, x, y, "Squad"); + } else if (detail_mode == DETAIL_MODE_JOB) { + OutputString(15, x, y, "Job"); + } else { + OutputString(15, x, y, "Profession"); + } break; case ALTSORT_STRESS: OutputString(15, x, y, "Stress Level"); From 2549f116a0498118fd5e4f94bfb1b6415ee0c827 Mon Sep 17 00:00:00 2001 From: James Logsdon Date: Mon, 23 Feb 2015 16:52:18 -0500 Subject: [PATCH 13/15] Custom Profession Templates in manipulator! --- NEWS | 1 + plugins/manipulator.cpp | 347 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+) diff --git a/NEWS b/NEWS index 500217946..ac86488b6 100644 --- a/NEWS +++ b/NEWS @@ -98,6 +98,7 @@ DFHack 0.40.24-r0 Misc Improvements added support for searching more lists manipulator: current job as a view mode (in addition to profession and squad) + manipulator: custom profession templates, with masking DFHack 0.40.23-r1 Internals diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index faa36ca19..bf7ca6206 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -268,6 +268,91 @@ const SkillColumn columns[] = { {20, 5, profession::NONE, unit_labor::NONE, job_skill::MAGIC_NATURE, "Dr"}, }; +typedef std::map TTokenToLabor; +static TTokenToLabor token_labors = { + {"MINE", unit_labor::MINE}, + {"HAUL_STONE", unit_labor::HAUL_STONE}, + {"HAUL_WOOD", unit_labor::HAUL_WOOD}, + {"HAUL_BODY", unit_labor::HAUL_BODY}, + {"HAUL_FOOD", unit_labor::HAUL_FOOD}, + {"HAUL_REFUSE", unit_labor::HAUL_REFUSE}, + {"HAUL_ITEM", unit_labor::HAUL_ITEM}, + {"HAUL_FURNITURE", unit_labor::HAUL_FURNITURE}, + {"HAUL_ANIMALS", unit_labor::HAUL_ANIMALS}, + {"CLEAN", unit_labor::CLEAN}, + {"CUTWOOD", unit_labor::CUTWOOD}, + {"CARPENTER", unit_labor::CARPENTER}, + {"DETAIL", unit_labor::DETAIL}, + {"MASON", unit_labor::MASON}, + {"ARCHITECT", unit_labor::ARCHITECT}, + {"ANIMALTRAIN", unit_labor::ANIMALTRAIN}, + {"ANIMALCARE", unit_labor::ANIMALCARE}, + {"DIAGNOSE", unit_labor::DIAGNOSE}, + {"SURGERY", unit_labor::SURGERY}, + {"BONE_SETTING", unit_labor::BONE_SETTING}, + {"SUTURING", unit_labor::SUTURING}, + {"DRESSING_WOUNDS", unit_labor::DRESSING_WOUNDS}, + {"FEED_WATER_CIVILIANS", unit_labor::FEED_WATER_CIVILIANS}, + {"RECOVER_WOUNDED", unit_labor::RECOVER_WOUNDED}, + {"BUTCHER", unit_labor::BUTCHER}, + {"TRAPPER", unit_labor::TRAPPER}, + {"DISSECT_VERMIN", unit_labor::DISSECT_VERMIN}, + {"LEATHER", unit_labor::LEATHER}, + {"TANNER", unit_labor::TANNER}, + {"BREWER", unit_labor::BREWER}, + {"ALCHEMIST", unit_labor::ALCHEMIST}, + {"SOAP_MAKER", unit_labor::SOAP_MAKER}, + {"WEAVER", unit_labor::WEAVER}, + {"CLOTHESMAKER", unit_labor::CLOTHESMAKER}, + {"MILLER", unit_labor::MILLER}, + {"PROCESS_PLANT", unit_labor::PROCESS_PLANT}, + {"MAKE_CHEESE", unit_labor::MAKE_CHEESE}, + {"MILK", unit_labor::MILK}, + {"COOK", unit_labor::COOK}, + {"PLANT", unit_labor::PLANT}, + {"HERBALIST", unit_labor::HERBALIST}, + {"FISH", unit_labor::FISH}, + {"CLEAN_FISH", unit_labor::CLEAN_FISH}, + {"DISSECT_FISH", unit_labor::DISSECT_FISH}, + {"HUNT", unit_labor::HUNT}, + {"SMELT", unit_labor::SMELT}, + {"FORGE_WEAPON", unit_labor::FORGE_WEAPON}, + {"FORGE_ARMOR", unit_labor::FORGE_ARMOR}, + {"FORGE_FURNITURE", unit_labor::FORGE_FURNITURE}, + {"METAL_CRAFT", unit_labor::METAL_CRAFT}, + {"CUT_GEM", unit_labor::CUT_GEM}, + {"ENCRUST_GEM", unit_labor::ENCRUST_GEM}, + {"WOOD_CRAFT", unit_labor::WOOD_CRAFT}, + {"STONE_CRAFT", unit_labor::STONE_CRAFT}, + {"BONE_CARVE", unit_labor::BONE_CARVE}, + {"GLASSMAKER", unit_labor::GLASSMAKER}, + {"EXTRACT_STRAND", unit_labor::EXTRACT_STRAND}, + {"SIEGECRAFT", unit_labor::SIEGECRAFT}, + {"SIEGEOPERATE", unit_labor::SIEGEOPERATE}, + {"BOWYER", unit_labor::BOWYER}, + {"MECHANIC", unit_labor::MECHANIC}, + {"POTASH_MAKING", unit_labor::POTASH_MAKING}, + {"LYE_MAKING", unit_labor::LYE_MAKING}, + {"DYER", unit_labor::DYER}, + {"BURN_WOOD", unit_labor::BURN_WOOD}, + {"OPERATE_PUMP", unit_labor::OPERATE_PUMP}, + {"SHEARER", unit_labor::SHEARER}, + {"SPINNER", unit_labor::SPINNER}, + {"POTTERY", unit_labor::POTTERY}, + {"GLAZING", unit_labor::GLAZING}, + {"PRESSING", unit_labor::PRESSING}, + {"BEEKEEPING", unit_labor::BEEKEEPING}, + {"WAX_WORKING", unit_labor::WAX_WORKING}, + {"HANDLE_VEHICLES", unit_labor::HANDLE_VEHICLES}, + {"HAUL_TRADE", unit_labor::HAUL_TRADE}, + {"PULL_LEVER", unit_labor::PULL_LEVER}, + {"REMOVE_CONSTRUCTION", unit_labor::REMOVE_CONSTRUCTION}, + {"HAUL_WATER", unit_labor::HAUL_WATER}, + {"GELD", unit_labor::GELD}, + {"BUILD_ROAD", unit_labor::BUILD_ROAD}, + {"BUILD_CONSTRUCTION", unit_labor::BUILD_CONSTRUCTION} +}; + struct UnitInfo { df::unit *unit; @@ -698,6 +783,131 @@ namespace unit_ops { } } +struct ProfessionTemplate +{ + std::string name; + bool mask; + std::vector labors; + + bool load(string directory, string file) + { + cerr << "Attempt to load " << file << endl; + std::ifstream infile(directory + "/" + file); + if (infile.bad()) { + return false; + } + + std::string line; + name = file; // If no name is given we default to the filename + mask = false; + while (std::getline(infile, line)) { + if (strcmp(line.substr(0,5).c_str(),"NAME ")==0) + { + auto nextInd = line.find(' '); + name = line.substr(nextInd + 1); + continue; + } + if (line.compare("MASK")==0) + { + mask = true; + continue; + } + + for (TTokenToLabor::const_iterator it = token_labors.begin(); it != token_labors.end(); ++it) + if (line.compare(it->first) == 0) + labors.push_back(it->second); + } + + return true; + } + bool save(string directory) + { + std::ofstream outfile(directory + "/" + name); + if (outfile.bad()) + return false; + + outfile << "NAME " << name << std::endl; + if (mask) + outfile << "MASK" << std::endl; + + for (TTokenToLabor::const_iterator it = token_labors.begin(); it != token_labors.end(); ++it) + if (hasLabor(it->second)) + outfile << it->first << std::endl; + + outfile.flush(); + outfile.close(); + return true; + } + + void apply(UnitInfo* u) + { + if (!mask && name.size() > 0) + unit_ops::set_profname(u, name); + + for (TTokenToLabor::const_iterator it = token_labors.begin(); it != token_labors.end(); ++it) + { + bool status = hasLabor(it->second); + if (mask && status) { + u->unit->status.labors[it->second] = status; + } else if (!mask) { + u->unit->status.labors[it->second] = status; + } + } + } + + bool hasLabor (df::unit_labor labor) + { + return std::find(labors.begin(), labors.end(), labor) != labors.end(); + } +}; + +static std::string professions_folder = Filesystem::getcwd() + "/professions"; +class ProfessionTemplateManager +{ +public: + std::vector templates; + + void reload() { + unload(); + load(); + } + void unload() { + templates.clear(); + } + void load() + { + vector files; + + cerr << "Attempting to load professions: " << professions_folder.c_str() << endl; + Filesystem::listdir(professions_folder, files); + for(size_t i = 0; i < files.size(); i++) + { + if (files[i].compare(".") == 0 || files[i].compare("..") == 0) + continue; + + ProfessionTemplate t; + if (t.load(professions_folder, files[i])) + { + templates.push_back(t); + } + } + } + void save_from_unit(UnitInfo *unit) + { + ProfessionTemplate t = { + unit_ops::get_profname(unit) + }; + + for (TTokenToLabor::const_iterator it = token_labors.begin(); it != token_labors.end(); ++it) + if (unit->unit->status.labors[it->second]) + t.labors.push_back(it->second); + + t.save(professions_folder); + reload(); + } +}; +static ProfessionTemplateManager manager; + class viewscreen_unitbatchopst : public dfhack_viewscreen { public: enum page { MENU, NICKNAME, PROFNAME }; @@ -879,6 +1089,118 @@ private: menu_options.resize(); } }; +class viewscreen_unitprofessionset : public dfhack_viewscreen { +public: + viewscreen_unitprofessionset(vector &base_units, + bool filter_selected = true + ) + { + menu_options.multiselect = false; + menu_options.auto_select = true; + menu_options.allow_search = false; + menu_options.left_margin = 2; + menu_options.bottom_margin = 2; + menu_options.clear(); + + manager.reload(); + for (size_t i = 0; i < manager.templates.size(); i++) { + std::string name = manager.templates[i].name; + if (manager.templates[i].mask) + name += " (mask)"; + ListEntry elem(name, i+1); + menu_options.add(elem); + } + menu_options.filterDisplay(); + + selection_empty = true; + for (auto it = base_units.begin(); it != base_units.end(); ++it) + { + UnitInfo* uinfo = *it; + if (uinfo->selected || !filter_selected) + { + selection_empty = false; + units.push_back(uinfo); + } + } + } + std::string getFocusString() { return "unitlabors/profession"; } + void feed(set *events) + { + if (events->count(interface_key::LEAVESCREEN)) + { + Screen::dismiss(this); + return; + } + if (menu_options.feed(events)) + { + // Allow left mouse button to trigger menu options + if (menu_options.feed_mouse_set_highlight) + events->insert(interface_key::SELECT); + else + return; + } + if (events->count(interface_key::SELECT)) + { + select_profession(menu_options.getFirstSelectedElem()); + Screen::dismiss(this); + return; + } + } + void select_profession(size_t selected) + { + ProfessionTemplate prof = manager.templates[selected - 1]; + + for (auto it = units.begin(); it != units.end(); ++it) + { + UnitInfo* u = (*it); + if (!u || !u->unit || !u->allowEdit) continue; + prof.apply(u); + } + } + void render() + { + dfhack_viewscreen::render(); + Screen::clear(); + int x = 2, y = 2; + Screen::drawBorder(" Dwarf Manipulator - Custom Profession "); + if (selection_empty) + { + OutputString(COLOR_LIGHTRED, x, y, "No dwarves selected!"); + return; + } + menu_options.display(true); + OutputString(COLOR_LIGHTGREEN, x, y, itos(units.size())); + OutputString(COLOR_GREY, x, y, string(" ") + (units.size() > 1 ? "dwarves" : "dwarf") + " selected: "); + int max_x = gps->dimx - 2; + size_t i = 0; + for ( ; i < units.size(); i++) + { + string name = unit_ops::get_nickname(units[i]); + if (name.size() + x + 12 >= max_x) // 12 = "and xxx more" + break; + OutputString(COLOR_WHITE, x, y, name + ", "); + } + if (i == units.size()) + { + x -= 2; + OutputString(COLOR_WHITE, x, y, " "); + } + else + { + OutputString(COLOR_GREY, x, y, "and " + itos(units.size() - i) + " more"); + } + } +protected: + bool selection_empty; + ListColumn menu_options; + vector units; +private: + void resize(int32_t x, int32_t y) + { + dfhack_viewscreen::resize(x, y); + menu_options.resize(); + } +}; enum display_columns { DISP_COLUMN_STRESS, @@ -1610,6 +1932,27 @@ void viewscreen_unitlaborsst::feed(set *events) Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names)); } + if (events->count(interface_key::CUSTOM_P)) + { + bool has_selected = false; + for (size_t i = 0; i < units.size(); i++) + if (units[i]->selected) + has_selected = true; + + if (has_selected) { + Screen::show(new viewscreen_unitprofessionset(units, true)); + } else { + vector tmp; + tmp.push_back(cur); + Screen::show(new viewscreen_unitprofessionset(tmp, false)); + } + } + + if (events->count(interface_key::CUSTOM_SHIFT_P)) + { + manager.save_from_unit(cur); + } + if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) { if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE)) @@ -1926,6 +2269,10 @@ void viewscreen_unitlaborsst::render() OutputString(15, x, y, ": Batch "); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_E)); OutputString(15, x, y, ": Edit "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_P)); + OutputString(15, x, y, ": Apply Profession "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_SHIFT_P)); + OutputString(15, x, y, ": Save Profession "); } df::unit *viewscreen_unitlaborsst::getSelectedUnit() From be2349d67deea459fbef4a4f01bd0c180dd7be25 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Mar 2015 16:32:17 -0500 Subject: [PATCH 14/15] Revert "Expose manipulator columns to Lua" This reverts commit 1d8b2d8cea8b7d4d9c3d423f439a4c44dfaaae60. --- plugins/CMakeLists.txt | 2 +- plugins/lua/manipulator.lua | 17 -------- plugins/manipulator.cpp | 87 ------------------------------------- 3 files changed, 1 insertion(+), 105 deletions(-) delete mode 100644 plugins/lua/manipulator.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index caa03cfba..063e8a90e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -132,7 +132,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) - DFHACK_PLUGIN(manipulator manipulator.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(mode mode.cpp) #DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) diff --git a/plugins/lua/manipulator.lua b/plugins/lua/manipulator.lua deleted file mode 100644 index a6dec63b0..000000000 --- a/plugins/lua/manipulator.lua +++ /dev/null @@ -1,17 +0,0 @@ -local _ENV = mkmodule('plugins.manipulator') - ---[[ - - Native functions: - - * isEnabled() - * setEnabled(bool) - * has_column(name) - * add_column(name, allow_column, allow_format, column_header, format_spec, format_desc) - * remove_column(name) - * list_columns() - ---]] - - -return _ENV diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index bf7ca6206..17c3a6594 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -5,8 +5,6 @@ #include #include #include -#include -#include #include #include #include @@ -515,91 +513,6 @@ bool sortBySelected (const UnitInfo *d1, const UnitInfo *d2) return descending ? (d1->selected > d2->selected) : (d1->selected < d2->selected); } -class ManipulatorColumn { -public: - typedef string(*callback)(df::unit*); - ManipulatorColumn(bool allow_column, bool allow_format, - string column_header = "", - string format_spec = "", - string format_desc = "") - :allow_column(allow_column), allow_format(allow_format), - column_header(column_header), format_spec(format_spec), format_desc(format_desc) - { } - bool allow_column; - bool allow_format; - string column_header; - string format_spec; - string format_desc; -}; - -class ManipulatorColumnList : public std::map { -public: - bool contains(string key) { return find(key) != end(); } - void remove(string key) { if (contains(key)) erase(key); } -}; - -ManipulatorColumnList column_list; - -bool has_column (string id) { return column_list.contains(id); } - -bool add_column (string id, bool allow_column, bool allow_format, - string column_header, string format_spec, string format_desc) -{ - if (!column_list.contains(id)) - { - ManipulatorColumn col(allow_column, allow_format, column_header, format_spec, format_desc); - column_list.insert(std::pair(id, col)); - return true; - } - return false; -} - -bool remove_column (string id) -{ - if (column_list.contains(id)) - { - column_list.remove(id); - return true; - } - return false; -} - -static int list_columns (lua_State *L) -{ - lua_newtable(L); - int clist_idx = lua_gettop(L); - int i = 1; - for (auto it = column_list.begin(); it != column_list.end(); ++it) - { - lua_newtable(L); - int col_idx = lua_gettop(L); - std::string foo = it->first; - std::string format_spec = it->second.format_spec; - #define set_field(key) Lua::SetField(L, it->second.key, col_idx, #key) - set_field(allow_column); - set_field(allow_format); - set_field(column_header); - set_field(format_spec); - set_field(format_desc); - #undef set_field - lua_rawseti(L, -2, i); - i++; - } - return 1; -} - -DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(has_column), - DFHACK_LUA_FUNCTION(add_column), - DFHACK_LUA_FUNCTION(remove_column), - DFHACK_LUA_END -}; - -DFHACK_PLUGIN_LUA_COMMANDS { - DFHACK_LUA_COMMAND(list_columns), - DFHACK_LUA_END -}; - template class StringFormatter { public: From 6f276ac41922bb31bc502bf33b629fc27a331868 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 5 Mar 2015 16:48:11 -0500 Subject: [PATCH 15/15] Shorten option width to stay within 80 columns --- plugins/manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 17c3a6594..9d0096d8e 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -2185,7 +2185,7 @@ void viewscreen_unitlaborsst::render() OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_P)); OutputString(15, x, y, ": Apply Profession "); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CUSTOM_SHIFT_P)); - OutputString(15, x, y, ": Save Profession "); + OutputString(15, x, y, ": Save Prof. "); } df::unit *viewscreen_unitlaborsst::getSelectedUnit()