From 6a54878a6ba52c9ee524f9c525c0c05734ee34c6 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 20 Oct 2012 01:38:08 +1300 Subject: [PATCH 01/24] Fix autobutcher resume --- plugins/zone.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c496f49b6..8e73ba34b 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3411,7 +3411,6 @@ command_result start_autobutcher(color_ostream &out) if (!config_autobutcher.isValid()) { config_autobutcher = pworld->AddPersistentData("autobutcher/config"); - config_autobutcher.ival(0) = enable_autobutcher; config_autobutcher.ival(1) = sleep_autobutcher; config_autobutcher.ival(2) = enable_autobutcher_autowatch; config_autobutcher.ival(3) = default_fk; @@ -3420,6 +3419,7 @@ command_result start_autobutcher(color_ostream &out) config_autobutcher.ival(6) = default_ma; } + config_autobutcher.ival(0) = enable_autobutcher; out << "Starting autobutcher." << endl; init_autobutcher(out); return CR_OK; From fd60db44ab415c0e59266380133c64ef3230b660 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 20 Oct 2012 01:38:28 +1300 Subject: [PATCH 02/24] Search plugin, early work. Unit and stocks screen. --- plugins/CMakeLists.txt | 1 + plugins/search.cpp | 327 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 plugins/search.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..fc3d0743e 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(search search.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/search.cpp b/plugins/search.cpp new file mode 100644 index 000000000..6a5a713c9 --- /dev/null +++ b/plugins/search.cpp @@ -0,0 +1,327 @@ +// Dwarf Manipulator - a Therapist-style labor editor + +#include +#include +#include +#include + +#include + +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_storesst.h" +#include "df/interface_key.h" + +using std::set; +using std::vector; +using std::string; + +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; + + +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(); +} + +struct search_struct +{ + static string search_string; + static bool entry_mode; + + void print_search_option(int &x) + { + OutputString(12, x, gps->dimy - 2, "s"); + OutputString(15, x, gps->dimy - 2, ": Search"); + if (search_string.length() > 0 || entry_mode) + OutputString(15, x, gps->dimy - 2, ": " + search_string); + if (entry_mode) + OutputString(12, x, gps->dimy - 2, "_"); + } + + bool process_input(df::interface_key select_key, set *input, bool &string_changed) + { + bool key_processed = true; + string_changed = false; + + if (entry_mode) + { + df::interface_key last_token = *input->rbegin(); + if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) + { + search_string += 32 + last_token - interface_key::STRING_A032; + string_changed = true; + } + else if (last_token == interface_key::STRING_A000) + { + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + string_changed = true; + } + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + entry_mode = false; + } + else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) + { + entry_mode = false; + key_processed = false; + } + } + else if (input->count(select_key)) + { + entry_mode = true; + } + else + { + key_processed = false; + } + + return key_processed; + } +}; + +string search_struct::search_string = ""; +bool search_struct::entry_mode = false; + + +struct stocks_search : df::viewscreen_storesst, search_struct +{ + typedef df::viewscreen_storesst interpose_base; + + static vector saved_items; + + static void reset_search() + { + entry_mode = false; + search_string = ""; + saved_items.clear(); + } + + void clear_search() + { + if (saved_items.size() > 0) + { + items = saved_items; + } + } + + + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + int x = 1; + print_search_option(x); + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (in_group_mode) + { + INTERPOSE_NEXT(feed)(input); + return; + } + + + bool string_changed = false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !in_right_list) + { + saved_items.clear(); + entry_mode = false; + if (search_string.length() > 0) + string_changed = true; + INTERPOSE_NEXT(feed)(input); + } + else + { + if (!process_input(interface_key::CUSTOM_S, input, string_changed) && !entry_mode) + { + INTERPOSE_NEXT(feed)(input); + if (in_group_mode) + { + clear_search(); + reset_search(); + } + } + } + + if (string_changed) + { + if (search_string.length() == 0) + { + clear_search(); + return; + } + + if (saved_items.size() == 0 && items.size() > 0) + { + saved_items = items; + } + items.clear(); + + for (int i = 0; i < saved_items.size(); i++ ) + { + string search_string_l = toLower(search_string); + string desc = Items::getDescription(saved_items[i], 0, true); + if (desc.find(search_string_l) != string::npos) + { + items.push_back(saved_items[i]); + } + } + + item_cursor = 0; + } + } + +}; + +vector stocks_search::saved_items; + + +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, feed); +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, render); + + + + +struct unitlist_search : df::viewscreen_unitlistst, search_struct +{ + typedef df::viewscreen_unitlistst interpose_base; + + static vector saved_units; + static vector saved_jobs; + + + static void reset_search() + { + entry_mode = false; + search_string = ""; + saved_units.clear(); + saved_jobs.clear(); + } + + void clear_search() + { + if (saved_units.size() > 0) + { + units[page] = saved_units; + jobs[page] = saved_jobs; + } + } + + void do_search() + { + if (search_string.length() == 0) + { + clear_search(); + return; + } + + while(1) + { + if (saved_units.size() == 0) + { + saved_units = units[page]; + saved_jobs = jobs[page]; + } + units[page].clear(); + jobs[page].clear(); + + for (int i = 0; i < saved_units.size(); i++ ) + { + df::unit *unit = saved_units[i]; + string search_string_l = toLower(search_string); + string name = toLower(Translation::TranslateName(Units::getVisibleName(unit), false)); + if (name.find(search_string_l) != string::npos) + { + units[page].push_back(unit); + jobs[page].push_back(saved_jobs[i]); + } + } + + if (units[page].size() > 0) + { + cursor_pos[page] = 0; + break; + } + + search_string.erase(search_string.length()-1); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + bool string_changed = false; + if (!process_input(interface_key::CUSTOM_S, input, string_changed)) + { + if (!entry_mode) + { + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + clear_search(); + reset_search(); + } + INTERPOSE_NEXT(feed)(input); + } + } + else if (string_changed) + do_search(); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (units[page].size()) + { + int x = 28; + print_search_option(x); + } + } + +}; + +vector unitlist_search::saved_units; +vector unitlist_search::saved_jobs; + + +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, feed); +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, render); + + + + +DFHACK_PLUGIN("search"); + + +DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) +{ + if (!gps || !INTERPOSE_HOOK(unitlist_search, feed).apply() || !INTERPOSE_HOOK(unitlist_search, render).apply() + || !INTERPOSE_HOOK(stocks_search, feed).apply() || !INTERPOSE_HOOK(stocks_search, render).apply()) + out.printerr("Could not insert Search hooks!\n"); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + INTERPOSE_HOOK(unitlist_search, feed).remove(); + INTERPOSE_HOOK(unitlist_search, render).remove(); + INTERPOSE_HOOK(stocks_search, feed).remove(); + INTERPOSE_HOOK(stocks_search, render).remove(); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) +{ + unitlist_search::reset_search(); + return CR_OK; +} \ No newline at end of file From c5b38a24eb86e9ec28eb290ad2aa3a0a2b5781e4 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 21 Oct 2012 14:18:29 +1300 Subject: [PATCH 03/24] Refactoring to use templates --- plugins/search.cpp | 576 +++++++++++++++++++++++++-------------------- 1 file changed, 316 insertions(+), 260 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 6a5a713c9..e911a73be 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -20,283 +20,338 @@ using namespace df::enums; using df::global::gps; - 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(); } -struct search_struct + +// +// START: Base Search functionality +// +template +struct search_parent { - static string search_string; - static bool entry_mode; - - void print_search_option(int &x) - { - OutputString(12, x, gps->dimy - 2, "s"); - OutputString(15, x, gps->dimy - 2, ": Search"); - if (search_string.length() > 0 || entry_mode) - OutputString(15, x, gps->dimy - 2, ": " + search_string); - if (entry_mode) - OutputString(12, x, gps->dimy - 2, "_"); - } - - bool process_input(df::interface_key select_key, set *input, bool &string_changed) - { - bool key_processed = true; - string_changed = false; - - if (entry_mode) - { - df::interface_key last_token = *input->rbegin(); - if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) - { - search_string += 32 + last_token - interface_key::STRING_A032; - string_changed = true; - } - else if (last_token == interface_key::STRING_A000) - { - if (search_string.length() > 0) - { - search_string.erase(search_string.length()-1); - string_changed = true; - } - } - else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) - { - entry_mode = false; - } - else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) - { - entry_mode = false; - key_processed = false; - } - } - else if (input->count(select_key)) - { - entry_mode = true; - } - else - { - key_processed = false; - } - - return key_processed; - } + vector *sort_list1; + vector *sort_list2; + int *cursor_pos; + char select_key; + const S *viewscreen; + + bool valid; + bool entry_mode; + bool redo_search; + string search_string; + vector saved_list1; + vector saved_list2; + + df::interface_key select_token; + const int ascii_to_enum_offset; + + search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0') + { + reset_all(); + } + + virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') + { + this->cursor_pos = cursor_pos; + this->sort_list1 = sort_list1; + this->sort_list2 = sort_list2; + this->select_key = select_key; + select_token = (df::interface_key) (ascii_to_enum_offset + select_key); + valid = true; + } + + void reset_search() + { + entry_mode = false; + search_string = ""; + saved_list1.clear(); + saved_list2.clear(); + } + + void reset_all() + { + reset_search(); + valid = false; + sort_list1 = NULL; + sort_list2 = NULL; + viewscreen = NULL; + select_key = 's'; + } + + void clear_search() + { + if (saved_list1.size() > 0) + { + *sort_list1 = saved_list1; + if (sort_list2 != NULL) + *sort_list2 = saved_list2; + } + } + + void do_search() + { + if (search_string.length() == 0) + { + clear_search(); + return; + } + + if (saved_list1.size() == 0) + { + saved_list1 = *sort_list1; + if (sort_list2 != NULL) + saved_list2 = *sort_list2; + } + sort_list1->clear(); + if (sort_list2 != NULL) + sort_list2->clear(); + + string search_string_l = toLower(search_string); + for (int i = 0; i < saved_list1.size(); i++ ) + { + T *element = saved_list1[i]; + string desc = toLower(get_element_description(element)); + if (desc.find(search_string_l) != string::npos) + { + sort_list1->push_back(element); + if (sort_list2 != NULL) + sort_list2->push_back(saved_list2[i]); + } + } + + *cursor_pos = 0; + } + + virtual bool process_input(const set *input) + { + bool key_processed = true; + + if (entry_mode) + { + df::interface_key last_token = *input->rbegin(); + if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) + { + search_string += last_token - ascii_to_enum_offset; + do_search(); + } + else if (last_token == interface_key::STRING_A000) + { + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + do_search(); + } + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + entry_mode = false; + } + else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) + || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + entry_mode = false; + key_processed = false; + } + } + else if (input->count(select_token)) + { + entry_mode = true; + } + else + { + key_processed = false; + } + + return key_processed; + } + + + virtual string get_element_description(T *element) const = 0; + virtual void render () const = 0; + + virtual void do_post_update_check() + { + if (redo_search) + { + do_search(); + redo_search = false; + } + } + + void print_search_option(int x) const + { + OutputString((entry_mode) ? 4 : 12, x, gps->dimy - 2, string(1, select_key)); + OutputString((entry_mode) ? 10 : 15, x, gps->dimy - 2, ": Search"); + if (search_string.length() > 0 || entry_mode) + OutputString(15, x, gps->dimy - 2, ": " + search_string); + if (entry_mode) + OutputString(10, x, gps->dimy - 2, "_"); + } }; -string search_struct::search_string = ""; -bool search_struct::entry_mode = false; - -struct stocks_search : df::viewscreen_storesst, search_struct +template +struct search_hook : T { - typedef df::viewscreen_storesst interpose_base; - - static vector saved_items; - - static void reset_search() - { - entry_mode = false; - search_string = ""; - saved_items.clear(); - } - - void clear_search() - { - if (saved_items.size() > 0) - { - items = saved_items; - } - } - - - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - - int x = 1; - print_search_option(x); - } - - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) - { - if (in_group_mode) - { - INTERPOSE_NEXT(feed)(input); - return; - } - - - bool string_changed = false; - - if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !in_right_list) - { - saved_items.clear(); - entry_mode = false; - if (search_string.length() > 0) - string_changed = true; - INTERPOSE_NEXT(feed)(input); - } - else - { - if (!process_input(interface_key::CUSTOM_S, input, string_changed) && !entry_mode) - { - INTERPOSE_NEXT(feed)(input); - if (in_group_mode) - { - clear_search(); - reset_search(); - } - } - } - - if (string_changed) - { - if (search_string.length() == 0) - { - clear_search(); - return; - } - - if (saved_items.size() == 0 && items.size() > 0) - { - saved_items = items; - } - items.clear(); - - for (int i = 0; i < saved_items.size(); i++ ) - { - string search_string_l = toLower(search_string); - string desc = Items::getDescription(saved_items[i], 0, true); - if (desc.find(search_string_l) != string::npos) - { - items.push_back(saved_items[i]); - } - } - - item_cursor = 0; - } - } - + typedef T interpose_base; + + static V module; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + module.init(this); + if (!module.process_input(input)) + { + INTERPOSE_NEXT(feed)(input); + module.do_post_update_check(); + } + + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + module.init(this); + INTERPOSE_NEXT(render)(); + module.render(); + } }; -vector stocks_search::saved_items; - +template V search_hook ::module; -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, feed); -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, render); +// +// END: Base Search functionality +// -struct unitlist_search : df::viewscreen_unitlistst, search_struct +// +// START: Stocks screen search +// +struct stocks_search : search_parent { - typedef df::viewscreen_unitlistst interpose_base; - - static vector saved_units; - static vector saved_jobs; - - - static void reset_search() - { - entry_mode = false; - search_string = ""; - saved_units.clear(); - saved_jobs.clear(); - } - - void clear_search() - { - if (saved_units.size() > 0) - { - units[page] = saved_units; - jobs[page] = saved_jobs; - } - } - - void do_search() - { - if (search_string.length() == 0) - { - clear_search(); - return; - } - - while(1) - { - if (saved_units.size() == 0) - { - saved_units = units[page]; - saved_jobs = jobs[page]; - } - units[page].clear(); - jobs[page].clear(); - - for (int i = 0; i < saved_units.size(); i++ ) - { - df::unit *unit = saved_units[i]; - string search_string_l = toLower(search_string); - string name = toLower(Translation::TranslateName(Units::getVisibleName(unit), false)); - if (name.find(search_string_l) != string::npos) - { - units[page].push_back(unit); - jobs[page].push_back(saved_jobs[i]); - } - } - - if (units[page].size() > 0) - { - cursor_pos[page] = 0; - break; - } - - search_string.erase(search_string.length()-1); - } - } - - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) - { - bool string_changed = false; - if (!process_input(interface_key::CUSTOM_S, input, string_changed)) - { - if (!entry_mode) - { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) - { - clear_search(); - reset_search(); - } - INTERPOSE_NEXT(feed)(input); - } - } - else if (string_changed) - do_search(); - } - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - - if (units[page].size()) - { - int x = 28; - print_search_option(x); - } - } + virtual void render() const + { + if (!viewscreen->in_group_mode) + print_search_option(1); + else + { + int x = 1; + OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); + } + } + + virtual string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + virtual bool process_input(const set *input) + { + if (viewscreen->in_group_mode) + return false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + { + saved_list1.clear(); + entry_mode = false; + if (search_string.length() > 0) + redo_search = true; + + return false; + } + else + return search_parent::process_input(input) || entry_mode; + + return true; + } + + virtual void do_post_update_check() + { + if (viewscreen->in_group_mode) + { + clear_search(); + reset_search(); + } + else + search_parent::do_post_update_check(); + } + + virtual void init(df::viewscreen_storesst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->item_cursor, &screen->items); + } + } }; -vector unitlist_search::saved_units; -vector unitlist_search::saved_jobs; +typedef search_hook stocks_search_hook; +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); + +// +// END: Stocks screen search +// -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, feed); -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, render); +// +// START: Unit screen search +// +struct unitlist_search : search_parent +{ + virtual void render() const + { + print_search_option(28); + } + + virtual string get_element_description(df::unit *element) const + { + return Translation::TranslateName(Units::getVisibleName(element), false); + } + + virtual bool process_input(const set *input) + { + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + if (!entry_mode) + { + clear_search(); + reset_search(); + return false; + } + } + else + return search_parent::process_input(input) || entry_mode; + + return true; + } + + virtual void init(df::viewscreen_unitlistst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); + } + } +}; + +typedef search_hook unitlist_search_hook; +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); + +// +// END: Unit screen search +// DFHACK_PLUGIN("search"); @@ -304,8 +359,8 @@ DFHACK_PLUGIN("search"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || !INTERPOSE_HOOK(unitlist_search, feed).apply() || !INTERPOSE_HOOK(unitlist_search, render).apply() - || !INTERPOSE_HOOK(stocks_search, feed).apply() || !INTERPOSE_HOOK(stocks_search, render).apply()) + if (!gps || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_hook, render).apply() + || !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || !INTERPOSE_HOOK(stocks_search_hook, render).apply()) out.printerr("Could not insert Search hooks!\n"); return CR_OK; @@ -313,15 +368,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Sun, 21 Oct 2012 21:32:01 +1300 Subject: [PATCH 04/24] Added Trade screen --- plugins/search.cpp | 365 ++++++++++++++++++++++++++++++--------------- 1 file changed, 245 insertions(+), 120 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index e911a73be..294ebe7d2 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -9,6 +9,7 @@ #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_tradegoodsst.h" #include "df/interface_key.h" using std::set; @@ -31,25 +32,93 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) // START: Base Search functionality // template -struct search_parent +class search_parent { - vector *sort_list1; - vector *sort_list2; - int *cursor_pos; - char select_key; +public: + void reset_all() + { + reset_search(); + valid = false; + sort_list1 = NULL; + sort_list2 = NULL; + viewscreen = NULL; + select_key = 's'; + } + + virtual bool process_input(set *input) + { + if (lock != NULL && lock != this) + return false; + + if (!should_check_input(input)) + return false; + + bool key_processed = true; + + if (entry_mode) + { + df::interface_key last_token = *input->rbegin(); + if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) + { + search_string += last_token - ascii_to_enum_offset; + do_search(); + } + else if (last_token == interface_key::STRING_A000) + { + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + do_search(); + } + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + end_entry_mode(); + } + else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) + || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + end_entry_mode(); + key_processed = false; + } + } + else if (input->count(select_token)) + { + start_entry_mode(); + } + else if (input->count((df::interface_key) (select_token + shift_offset))) + { + clear_search(); + } + else + { + key_processed = false; + } + + return key_processed || entry_mode; + } + + virtual void do_post_update_check() + { + if (redo_search) + { + do_search(); + redo_search = false; + } + } + + static search_parent *lock; + +protected: const S *viewscreen; + vector saved_list1; + vector saved_list2; bool valid; - bool entry_mode; bool redo_search; string search_string; - vector saved_list1; - vector saved_list2; - df::interface_key select_token; - const int ascii_to_enum_offset; - - search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0') + search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') { reset_all(); } @@ -63,23 +132,30 @@ struct search_parent select_token = (df::interface_key) (ascii_to_enum_offset + select_key); valid = true; } + + bool is_entry_mode() + { + return entry_mode; + } + + void start_entry_mode() + { + entry_mode = true; + lock = this; + } - void reset_search() + void end_entry_mode() { entry_mode = false; - search_string = ""; - saved_list1.clear(); - saved_list2.clear(); + lock = NULL; } - void reset_all() + void reset_search() { - reset_search(); - valid = false; - sort_list1 = NULL; - sort_list2 = NULL; - viewscreen = NULL; - select_key = 's'; + end_entry_mode(); + search_string = ""; + saved_list1.clear(); + saved_list2.clear(); } void clear_search() @@ -90,6 +166,7 @@ struct search_parent if (sort_list2 != NULL) *sort_list2 = saved_list2; } + search_string = ""; } void do_search() @@ -126,75 +203,45 @@ struct search_parent *cursor_pos = 0; } - virtual bool process_input(const set *input) + virtual bool should_check_input(set *input) { - bool key_processed = true; - - if (entry_mode) - { - df::interface_key last_token = *input->rbegin(); - if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) - { - search_string += last_token - ascii_to_enum_offset; - do_search(); - } - else if (last_token == interface_key::STRING_A000) - { - if (search_string.length() > 0) - { - search_string.erase(search_string.length()-1); - do_search(); - } - } - else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) - { - entry_mode = false; - } - else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) - || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) - { - entry_mode = false; - key_processed = false; - } - } - else if (input->count(select_token)) - { - entry_mode = true; - } - else - { - key_processed = false; - } - - return key_processed; + return true; } - - virtual string get_element_description(T *element) const = 0; - virtual void render () const = 0; - - virtual void do_post_update_check() + void print_search_option(int x, int y = -1) const { - if (redo_search) - { - do_search(); - redo_search = false; - } - } + if (y == -1) + y = gps->dimy - 2; - void print_search_option(int x) const - { - OutputString((entry_mode) ? 4 : 12, x, gps->dimy - 2, string(1, select_key)); - OutputString((entry_mode) ? 10 : 15, x, gps->dimy - 2, ": Search"); + OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key)); + OutputString((entry_mode) ? 10 : 15, x, y, ": Search"); if (search_string.length() > 0 || entry_mode) - OutputString(15, x, gps->dimy - 2, ": " + search_string); + OutputString(15, x, y, ": " + search_string); if (entry_mode) - OutputString(10, x, gps->dimy - 2, "_"); + OutputString(10, x, y, "_"); } + + virtual string get_element_description(T *element) const = 0; + virtual void render () const = 0; + +private: + vector *sort_list1; + vector *sort_list2; + int *cursor_pos; + char select_key; + + bool entry_mode; + + df::interface_key select_token; + const int ascii_to_enum_offset; + const int shift_offset; + }; +template search_parent *search_parent ::lock = NULL; -template + +template struct search_hook : T { typedef T interpose_base; @@ -220,7 +267,7 @@ struct search_hook : T } }; -template V search_hook ::module; +template V search_hook ::module; // @@ -232,8 +279,10 @@ template V search_hook ::module; // // START: Stocks screen search // -struct stocks_search : search_parent +class stocks_search : public search_parent { +public: + virtual void render() const { if (!viewscreen->in_group_mode) @@ -245,31 +294,6 @@ struct stocks_search : search_parent } } - virtual string get_element_description(df::item *element) const - { - return Items::getDescription(element, 0, true); - } - - virtual bool process_input(const set *input) - { - if (viewscreen->in_group_mode) - return false; - - if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) - { - saved_list1.clear(); - entry_mode = false; - if (search_string.length() > 0) - redo_search = true; - - return false; - } - else - return search_parent::process_input(input) || entry_mode; - - return true; - } - virtual void do_post_update_check() { if (viewscreen->in_group_mode) @@ -290,6 +314,30 @@ struct stocks_search : search_parent } } + +private: + virtual string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + virtual bool should_check_input(set *input) + { + if (viewscreen->in_group_mode) + return false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + { + saved_list1.clear(); + end_entry_mode(); + if (search_string.length() > 0) + redo_search = true; + + return false; + } + + return true; + } }; @@ -306,51 +354,120 @@ IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); // // START: Unit screen search // -struct unitlist_search : search_parent +class unitlist_search : public search_parent { +public: + virtual void render() const { print_search_option(28); } + virtual void init(df::viewscreen_unitlistst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); + } + } + +private: virtual string get_element_description(df::unit *element) const { return Translation::TranslateName(Units::getVisibleName(element), false); } - virtual bool process_input(const set *input) + virtual bool should_check_input(set *input) { if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) { - if (!entry_mode) + if (!is_entry_mode()) { clear_search(); reset_search(); - return false; } + else + input->clear(); + + return false; } - else - return search_parent::process_input(input) || entry_mode; return true; } - virtual void init(df::viewscreen_unitlistst *screen) +}; + +typedef search_hook unitlist_search_hook; +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); + +// +// END: Unit screen search +// + + +// +// START: Trade screen search +// +class trade_search_base : public search_parent +{ + +private: + virtual string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } +}; + + +class trade_search_merc : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(2, 26); + } + + virtual void init(df::viewscreen_tradegoodsst *screen) { if (!valid) { viewscreen = screen; - search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); + search_parent::init(&screen->trader_cursor, &screen->trader_items, NULL, 'q'); } } }; -typedef search_hook unitlist_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); +typedef search_hook trade_search_merc_hook; +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); + + +class trade_search_fort : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(42, 26); + } + + virtual void init(df::viewscreen_tradegoodsst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->broker_cursor, &screen->broker_items, NULL, 'w'); + } + } +}; + +typedef search_hook trade_search_fort_hook; +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); // -// END: Unit screen search +// END: Trade screen search // @@ -360,6 +477,8 @@ DFHACK_PLUGIN("search"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { if (!gps || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_hook, render).apply() + || !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() + || !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() || !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || !INTERPOSE_HOOK(stocks_search_hook, render).apply()) out.printerr("Could not insert Search hooks!\n"); @@ -370,6 +489,10 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { INTERPOSE_HOOK(unitlist_search_hook, feed).remove(); INTERPOSE_HOOK(unitlist_search_hook, render).remove(); + INTERPOSE_HOOK(trade_search_merc_hook, feed).remove(); + INTERPOSE_HOOK(trade_search_merc_hook, render).remove(); + INTERPOSE_HOOK(trade_search_fort_hook, feed).remove(); + INTERPOSE_HOOK(trade_search_fort_hook, render).remove(); INTERPOSE_HOOK(stocks_search_hook, feed).remove(); INTERPOSE_HOOK(stocks_search_hook, render).remove(); return CR_OK; @@ -378,6 +501,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) { unitlist_search_hook::module.reset_all(); + trade_search_merc_hook::module.reset_all(); + trade_search_fort_hook::module.reset_all(); stocks_search_hook::module.reset_all(); return CR_OK; } From f501ae074870ffd629a9e086d2acdc0026bd5d66 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 22 Oct 2012 01:09:10 +1300 Subject: [PATCH 05/24] Bug fix on unit sort --- plugins/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 294ebe7d2..9a9a28c40 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -385,7 +385,7 @@ private: if (!is_entry_mode()) { clear_search(); - reset_search(); + reset_all(); } else input->clear(); From c433a8eeff35911af1073beac87adbc7aab375c8 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 22 Oct 2012 02:42:17 +1300 Subject: [PATCH 06/24] Better handling of Trade screen. Tracks marked items and handles re-orders is sort plugin is used to sort filtered list. --- plugins/search.cpp | 88 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 9a9a28c40..81785d914 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -31,7 +31,7 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) // // START: Base Search functionality // -template +template class search_parent { public: @@ -43,6 +43,7 @@ public: sort_list2 = NULL; viewscreen = NULL; select_key = 's'; + track_secondary_values = false; } virtual bool process_input(set *input) @@ -111,11 +112,13 @@ public: protected: const S *viewscreen; - vector saved_list1; - vector saved_list2; + vector saved_list1, reference_list; + vector saved_list2; + vector saved_indexes; bool valid; bool redo_search; + bool track_secondary_values; string search_string; search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') @@ -123,13 +126,14 @@ protected: reset_all(); } - virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') + virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') { this->cursor_pos = cursor_pos; this->sort_list1 = sort_list1; this->sort_list2 = sort_list2; this->select_key = select_key; select_token = (df::interface_key) (ascii_to_enum_offset + select_key); + track_secondary_values = false; valid = true; } @@ -156,6 +160,43 @@ protected: search_string = ""; saved_list1.clear(); saved_list2.clear(); + reference_list.clear(); + saved_indexes.clear(); + } + + void update_secondary_values() + { + if (sort_list2 != NULL && track_secondary_values) + { + bool list_has_been_sorted = (sort_list1->size() == reference_list.size() + && *sort_list1 != reference_list); + + for (int i = 0; i < saved_indexes.size(); i++) + { + int adjusted_item_index = i; + if (list_has_been_sorted) + { + for (int j = 0; j < sort_list1->size(); j++) + { + if ((*sort_list1)[j] == reference_list[i]) + { + adjusted_item_index = j; + break; + } + } + } + + saved_list2[saved_indexes[i]] = (*sort_list2)[adjusted_item_index]; + } + saved_indexes.clear(); + } + } + + //Used to work out if filtered list has been sorted after filtering + void store_reference_values() + { + if (track_secondary_values) + reference_list = *sort_list1; } void clear_search() @@ -164,8 +205,12 @@ protected: { *sort_list1 = saved_list1; if (sort_list2 != NULL) + { + update_secondary_values(); *sort_list2 = saved_list2; + } } + store_reference_values(); search_string = ""; } @@ -183,23 +228,35 @@ protected: if (sort_list2 != NULL) saved_list2 = *sort_list2; } + else + update_secondary_values(); + sort_list1->clear(); if (sort_list2 != NULL) + { sort_list2->clear(); + saved_indexes.clear(); + } string search_string_l = toLower(search_string); for (int i = 0; i < saved_list1.size(); i++ ) { - T *element = saved_list1[i]; + T element = saved_list1[i]; string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) { sort_list1->push_back(element); if (sort_list2 != NULL) + { sort_list2->push_back(saved_list2[i]); + if (track_secondary_values) + saved_indexes.push_back(i); + } } } + store_reference_values(); + *cursor_pos = 0; } @@ -221,12 +278,12 @@ protected: OutputString(10, x, y, "_"); } - virtual string get_element_description(T *element) const = 0; + virtual string get_element_description(T element) const = 0; virtual void render () const = 0; private: - vector *sort_list1; - vector *sort_list2; + vector *sort_list1; + vector *sort_list2; int *cursor_pos; char select_key; @@ -237,7 +294,6 @@ private: const int shift_offset; }; - template search_parent *search_parent ::lock = NULL; @@ -279,7 +335,7 @@ template V search_hook ::module; // // START: Stocks screen search // -class stocks_search : public search_parent +class stocks_search : public search_parent { public: @@ -354,7 +410,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); // // START: Unit screen search // -class unitlist_search : public search_parent +class unitlist_search : public search_parent { public: @@ -410,7 +466,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); // // START: Trade screen search // -class trade_search_base : public search_parent +class trade_search_base : public search_parent { private: @@ -434,7 +490,8 @@ public: if (!valid) { viewscreen = screen; - search_parent::init(&screen->trader_cursor, &screen->trader_items, NULL, 'q'); + search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); + track_secondary_values = true; } } }; @@ -457,7 +514,8 @@ public: if (!valid) { viewscreen = screen; - search_parent::init(&screen->broker_cursor, &screen->broker_items, NULL, 'w'); + search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); + track_secondary_values = true; } } }; @@ -505,4 +563,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 2fcceaa65e8f3ff72c70af319fff09330b8baa8e Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 22 Oct 2012 21:44:02 +1300 Subject: [PATCH 07/24] Set priority over manipulator plugin Include animal type is search --- plugins/search.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 81785d914..6b9a94947 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -7,9 +7,10 @@ #include -#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" #include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_unitlistst.h" #include "df/interface_key.h" using std::set; @@ -431,12 +432,16 @@ public: private: virtual string get_element_description(df::unit *element) const { - return Translation::TranslateName(Units::getVisibleName(element), false); + string desc = Translation::TranslateName(Units::getVisibleName(element), false); + if (viewscreen->page == 1) + desc += Units::getProfessionName(element); + + return desc; } virtual bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L)) { if (!is_entry_mode()) { @@ -455,14 +460,22 @@ private: }; typedef search_hook unitlist_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search // +// +// TODO: Animals screen search +// + +// +// END: Animals screen search +// + // // START: Trade screen search // From 94898a73fb0be419c93b2237eae197e34dbea50d Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 23 Oct 2012 21:20:51 +1300 Subject: [PATCH 08/24] Add Search plugin comments --- plugins/search.cpp | 66 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 6b9a94947..fdc788955 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1,5 +1,3 @@ -// Dwarf Manipulator - a Therapist-style labor editor - #include #include #include @@ -7,7 +5,7 @@ #include -#include "df/viewscreen_petst.h" +//#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" @@ -22,20 +20,35 @@ using namespace df::enums; using df::global::gps; +/* +Search Plugin + +A plugin that adds a "Search" hotkey to some screens (Units, Trade and Stocks) +that allows filtering of the list items by a typed query. + +Works by manipulating the vector(s) that the list based viewscreens use to store +their items. When a search is started the plugin saves the original vectors and +with each keystroke creates a new filtered vector off the saves for the screen +to use. +*/ + + 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(); } - // // START: Base Search functionality // + +// Parent class that does most of the work template class search_parent { public: + // Called each time you enter or leave a searchable screen. Resets everything. void reset_all() { reset_search(); @@ -47,11 +60,15 @@ public: track_secondary_values = false; } + // A new keystroke is received in a searchable screen virtual bool process_input(set *input) { + // If the page has two search options (Trade screen), only allow one to operate + // at a time if (lock != NULL && lock != this) return false; + // Allows custom preprocessing for each screen if (!should_check_input(input)) return false; @@ -59,14 +76,18 @@ public: if (entry_mode) { + // 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; do_search(); } else if (last_token == interface_key::STRING_A000) { + // Backspace if (search_string.length() > 0) { search_string.erase(search_string.length()-1); @@ -75,31 +96,40 @@ public: } else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) { + // ENTER or ESC: leave typing mode end_entry_mode(); } else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) { + // Arrow key pressed. Leave entry mode and allow screen to process key end_entry_mode(); key_processed = false; } } + // Not in query typing mode else if (input->count(select_token)) { + // Hotkey pressed, enter typing mode start_entry_mode(); } else if (input->count((df::interface_key) (select_token + shift_offset))) { + // Shift + Hotkey pressed, clear query clear_search(); } else { + // Not a key for us, pass it on to the screen key_processed = false; } - return key_processed || entry_mode; + return key_processed || entry_mode; // Only pass unrecognized keys down if not in typing mode } + // Called if the search should be redone after the screen processes the keystroke. + // Used by the stocks screen where changing categories should redo the search on + // the new category. virtual void do_post_update_check() { if (redo_search) @@ -165,6 +195,10 @@ protected: saved_indexes.clear(); } + // If the second vector is editable (i.e. Trade screen vector used for marking). then it may + // have been edited while the list was filtered. We have to update the original unfiltered + // list with these values. Uses a stored reference vector to determine if the list has been + // reordered after filtering, in which case indexes must be remapped. void update_secondary_values() { if (sort_list2 != NULL && track_secondary_values) @@ -193,13 +227,14 @@ protected: } } - //Used to work out if filtered list has been sorted after filtering + // Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering void store_reference_values() { if (track_secondary_values) reference_list = *sort_list1; } + // Shortcut to clear the search immediately void clear_search() { if (saved_list1.size() > 0) @@ -215,6 +250,7 @@ protected: search_string = ""; } + // The actual sort void do_search() { if (search_string.length() == 0) @@ -225,13 +261,15 @@ protected: if (saved_list1.size() == 0) { + // On first run, save the original list saved_list1 = *sort_list1; if (sort_list2 != NULL) saved_list2 = *sort_list2; } else - update_secondary_values(); + update_secondary_values(); // Update original list with any modified values + // Clear viewscreen vectors sort_list1->clear(); if (sort_list2 != NULL) { @@ -251,12 +289,12 @@ protected: { sort_list2->push_back(saved_list2[i]); if (track_secondary_values) - saved_indexes.push_back(i); + saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed } } } - store_reference_values(); + store_reference_values(); //Keep a copy, in case user sorts new list *cursor_pos = 0; } @@ -266,6 +304,7 @@ protected: return true; } + // Display hotkey message void print_search_option(int x, int y = -1) const { if (y == -1) @@ -297,7 +336,7 @@ private: }; template search_parent *search_parent ::lock = NULL; - +// Parent struct for the hooks template struct search_hook : T { @@ -355,6 +394,7 @@ public: { if (viewscreen->in_group_mode) { + // Disable search if item lists are grouped clear_search(); reset_search(); } @@ -385,6 +425,7 @@ private: if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) { + // Redo search if category changes saved_list1.clear(); end_entry_mode(); if (search_string.length() > 0) @@ -434,7 +475,7 @@ private: { string desc = Translation::TranslateName(Units::getVisibleName(element), false); if (viewscreen->page == 1) - desc += Units::getProfessionName(element); + desc += Units::getProfessionName(element); // Check animal type too return desc; } @@ -445,11 +486,12 @@ private: { if (!is_entry_mode()) { + // Changing screens, reset search clear_search(); reset_all(); } else - input->clear(); + input->clear(); // Ignore cursor keys when typing return false; } From 0a78064467f75accc112eaf56dffacabc87535b9 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 31 Oct 2012 16:11:06 +0100 Subject: [PATCH 09/24] ruby: raise on invalid enum symbols --- plugins/ruby/ruby-autogen-defs.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index d5bcb08f4..c3203bd52 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -181,7 +181,8 @@ module DFHack @nume ||= const_get(:NUME) end - def self.int(i) + def self.int(i, allow_bad_sym=false) + raise ArgumentError, "invalid enum member #{i} of #{self}" if i.kind_of?(::Symbol) and not allow_bad_sym and not nume.has_key?(i) nume[i] || i end def self.sym(i) @@ -797,7 +798,6 @@ module DFHack def isset(key) raise unless @_memaddr key = @_enum.int(key) if _enum - raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_isset(@_memaddr, key) end alias is_set? isset @@ -805,14 +805,12 @@ module DFHack def set(key) raise unless @_memaddr key = @_enum.int(key) if _enum - raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_set(@_memaddr, key) end def delete(key) raise unless @_memaddr key = @_enum.int(key) if _enum - raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_deletekey(@_memaddr, key) end From 3257eb80a1104599b0ae57ccf3a09b38343242d9 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 2 Nov 2012 16:28:48 -0500 Subject: [PATCH 10/24] Add checks to avoid crashing if we encounter a soulless unit --- plugins/manipulator.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index b6d30ab1e..1bc0195b6 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -298,6 +298,10 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) { if (sort_skill != job_skill::NONE) { + if (!d1->unit->status.current_soul) + return !descending; + if (!d2->unit->status.current_soul) + return descending; df::unit_skill *s1 = binsearch_in_vector(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); df::unit_skill *s2 = binsearch_in_vector(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); int l1 = s1 ? s1->rating : 0; @@ -1030,7 +1034,9 @@ void viewscreen_unitlaborsst::render() fg = 9; if (columns[col_offset].skill != job_skill::NONE) { - df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); + df::unit_skill *skill = NULL; + if (unit->status.current_soul) + skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); if ((skill != NULL) && (skill->rating || skill->experience)) { int level = skill->rating; @@ -1086,7 +1092,9 @@ void viewscreen_unitlaborsst::render() } else { - df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); + df::unit_skill *skill = NULL; + if (unit->status.current_soul) + skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); if (skill) { int level = skill->rating; From 8bccfb1e9a197084ff645b05ee877209a2475556 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 14:31:07 +0400 Subject: [PATCH 11/24] Fix gcc compiler errors and warnings in search, reindent plugin_init, etc. --- NEWS | 3 +++ plugins/search.cpp | 53 +++++++++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index 0f9bf4c3d..d5080b05a 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ DFHack future Together with a couple of binary patches and the gui/assign-rack script, this plugin makes weapon racks, armor stands, chests and cabinets in properly designated barracks be used again for storage of squad equipment. + New Search plugin by falconne: + Adds an incremental search function to the Stocks, Trading and Unit List screens. + DFHack v0.34.11-r2 diff --git a/plugins/search.cpp b/plugins/search.cpp index fdc788955..e19c2e3c0 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -206,12 +206,12 @@ protected: bool list_has_been_sorted = (sort_list1->size() == reference_list.size() && *sort_list1 != reference_list); - for (int i = 0; i < saved_indexes.size(); i++) + 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 < sort_list1->size(); j++) + for (size_t j = 0; j < sort_list1->size(); j++) { if ((*sort_list1)[j] == reference_list[i]) { @@ -278,7 +278,7 @@ protected: } string search_string_l = toLower(search_string); - for (int i = 0; i < saved_list1.size(); i++ ) + for (size_t i = 0; i < saved_list1.size(); i++ ) { T element = saved_list1[i]; string desc = toLower(get_element_description(element)); @@ -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 @@ -502,8 +502,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 @@ -552,8 +552,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 @@ -576,8 +576,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 @@ -589,10 +589,15 @@ DFHACK_PLUGIN("search"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_hook, render).apply() - || !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() - || !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() - || !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || !INTERPOSE_HOOK(stocks_search_hook, render).apply()) + if (!gps || + !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || + !INTERPOSE_HOOK(unitlist_search_hook, render).apply() || + !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || + !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() || + !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || + !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() || + !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || + !INTERPOSE_HOOK(stocks_search_hook, render).apply()) out.printerr("Could not insert Search hooks!\n"); return CR_OK; @@ -613,9 +618,17 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) { - unitlist_search_hook::module.reset_all(); - trade_search_merc_hook::module.reset_all(); - trade_search_fort_hook::module.reset_all(); - stocks_search_hook::module.reset_all(); + switch (event) { + case SC_VIEWSCREEN_CHANGED: + unitlist_search_hook::module.reset_all(); + trade_search_merc_hook::module.reset_all(); + trade_search_fort_hook::module.reset_all(); + stocks_search_hook::module.reset_all(); + break; + + default: + break; + } + return CR_OK; -} \ No newline at end of file +} From 9bf24bde10154e335dfe96dfb1cb202dd9027cc1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 15:34:04 +0400 Subject: [PATCH 12/24] More various updates for search. - Keep the search state as long as the screen is alive. - Properly forget saved state when clearing search. - Fix the start column in render for stocks screen. - Allow search by profession in all Units pages. - Dismiss search when trying to trade. --- plugins/search.cpp | 102 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index e19c2e3c0..cc3f29c12 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -10,6 +10,7 @@ #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" #include "df/interface_key.h" +#include "df/interfacest.h" using std::set; using std::vector; @@ -19,6 +20,7 @@ using namespace DFHack; using namespace df::enums; using df::global::gps; +using df::global::gview; /* Search Plugin @@ -39,6 +41,14 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) x += text.length(); } +static bool is_live_screen(const df::viewscreen *screen) +{ + for (df::viewscreen *cur = &gview->view; cur; cur = cur->child) + if (cur == screen) + return true; + return false; +} + // // START: Base Search functionality // @@ -60,6 +70,15 @@ public: track_secondary_values = false; } + bool reset_on_change() + { + if (valid && is_live_screen(viewscreen)) + return false; + + reset_all(); + return true; + } + // A new keystroke is received in a searchable screen virtual bool process_input(set *input) { @@ -245,6 +264,9 @@ protected: update_secondary_values(); *sort_list2 = saved_list2; } + + saved_list1.clear(); + saved_list2.clear(); } store_reference_values(); search_string = ""; @@ -346,7 +368,12 @@ struct search_hook : T DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - module.init(this); + if (!module.init(this)) + { + INTERPOSE_NEXT(feed)(input); + return; + } + if (!module.process_input(input)) { INTERPOSE_NEXT(feed)(input); @@ -357,9 +384,10 @@ struct search_hook : T DEFINE_VMETHOD_INTERPOSE(void, render, ()) { - module.init(this); + bool ok = module.init(this); INTERPOSE_NEXT(render)(); - module.render(); + if (ok) + module.render(); } }; @@ -382,10 +410,10 @@ public: virtual void render() const { if (!viewscreen->in_group_mode) - print_search_option(1); + print_search_option(2); else { - int x = 1; + int x = 2; OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); } } @@ -402,13 +430,18 @@ public: search_parent::do_post_update_check(); } - virtual void init(df::viewscreen_storesst *screen) + bool init(df::viewscreen_storesst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->item_cursor, &screen->items); } + + return true; } @@ -461,21 +494,25 @@ public: print_search_option(28); } - virtual void init(df::viewscreen_unitlistst *screen) + bool init(df::viewscreen_unitlistst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); } + + return true; } private: virtual string get_element_description(df::unit *element) const { string desc = Translation::TranslateName(Units::getVisibleName(element), false); - if (viewscreen->page == 1) - desc += Units::getProfessionName(element); // Check animal type too + desc += ", " + Units::getProfessionName(element); // Check animal type too return desc; } @@ -529,6 +566,29 @@ private: { return Items::getDescription(element, 0, true); } + + virtual bool should_check_input(set *input) + { + if (is_entry_mode()) + return true; + + if (input->count(interface_key::TRADE_TRADE) || + input->count(interface_key::TRADE_OFFER) || + input->count(interface_key::TRADE_SEIZE)) + { + // Block the keys if were searching + if (!search_string.empty()) + input->clear(); + + // Trying to trade, reset search + clear_search(); + reset_all(); + + return false; + } + + return true; + } }; @@ -540,14 +600,19 @@ public: print_search_option(2, 26); } - virtual void init(df::viewscreen_tradegoodsst *screen) + bool init(df::viewscreen_tradegoodsst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); track_secondary_values = true; } + + return true; } }; @@ -564,14 +629,19 @@ public: print_search_option(42, 26); } - virtual void init(df::viewscreen_tradegoodsst *screen) + bool init(df::viewscreen_tradegoodsst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); track_secondary_values = true; } + + return true; } }; @@ -589,7 +659,7 @@ DFHACK_PLUGIN("search"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || + if (!gps || !gview || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_hook, render).apply() || !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || @@ -620,10 +690,10 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch { switch (event) { case SC_VIEWSCREEN_CHANGED: - unitlist_search_hook::module.reset_all(); - trade_search_merc_hook::module.reset_all(); - trade_search_fort_hook::module.reset_all(); - stocks_search_hook::module.reset_all(); + unitlist_search_hook::module.reset_on_change(); + trade_search_merc_hook::module.reset_on_change(); + trade_search_fort_hook::module.reset_on_change(); + stocks_search_hook::module.reset_on_change(); break; default: From d6f1bb93b5397338d9c6761ed9ce4d3a6d01e999 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 15:49:34 +0400 Subject: [PATCH 13/24] Add documentation for the search plugin. --- Readme.html | 104 +++++++++++++++++++++++++++++++--------------------- Readme.rst | 25 +++++++++++++ 2 files changed, 87 insertions(+), 42 deletions(-) diff --git a/Readme.html b/Readme.html index 0da481c88..cd073459c 100644 --- a/Readme.html +++ b/Readme.html @@ -500,33 +500,34 @@ access DF memory and allow for easier development of new tools.

  • In-game interface tools
  • -
  • Behavior Mods
      -
    • Siege Engine
        -
      • Rationale
      • -
      • Configuration UI
      • +
      • Behavior Mods @@ -2691,14 +2692,33 @@ cursor onto that cell instead of toggling it.
      • Pressing ESC normally returns to the unit screen, but Shift-ESC would exit directly to the main dwarf mode screen.

        +
        -

        gui/liquids

        +

        gui/liquids

        To use, bind to a key and activate in the 'k' mode.

        While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

        -

        gui/mechanisms

        +

        gui/mechanisms

        To use, bind to a key and activate in the 'q' mode.

        Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings.

        @@ -2707,7 +2727,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

        -

        gui/rename

        +

        gui/rename

        Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

          @@ -2723,14 +2743,14 @@ It is also possible to rename zones from the 'i' menu.

          The building or unit options are automatically assumed when in relevant ui state.

        -

        gui/room-list

        +

        gui/room-list

        To use, bind to a key and activate in the 'q' mode, either immediately or after opening the assign owner page.

        The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

        -

        gui/choose-weapons

        +

        gui/choose-weapons

        Bind to a key, and activate in the Equip->View/Customize page of the military screen.

        Depending on the cursor location, it rewrites all 'individual choice weapon' entries in the selected squad or position to use a specific weapon type matching the assigned @@ -2740,13 +2760,13 @@ only that entry, and does it even if it is not 'individual choice'.

        and may lead to inappropriate weapons being selected.

        -

        gui/guide-path

        +

        gui/guide-path

        Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

        The script displays the cached path that will be used by the order; the game computes it when the order is executed for the first time.

        -

        gui/workshop-job

        +

        gui/workshop-job

        Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

        The script shows a list of the input reagents of the selected job, and allows changing them like the job item-type and job item-material commands.

        @@ -2774,7 +2794,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

        -

        gui/workflow

        +

        gui/workflow

        Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

        This script provides a simple interface to constraints managed by the workflow plugin. When active, it displays a list of all constraints applicable to the @@ -2796,7 +2816,7 @@ as described in workflow documentation above. can be used for troubleshooting jobs that don't match the right constraints.

        -

        gui/assign-rack

        +

        gui/assign-rack

        Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

        This script is part of a group of related fixes to make the armory storage work again. The existing issues are:

        @@ -2816,7 +2836,7 @@ the intended user.

        -

        Behavior Mods

        +

        Behavior Mods

        These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

        @@ -2827,20 +2847,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

        -

        Siege Engine

        +

        Siege Engine

        The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

        -

        Rationale

        +

        Rationale

        Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

        -

        Configuration UI

        +

        Configuration UI

        The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

        The main mode displays the current target, selected ammo item type, linked stockpiles and @@ -2861,7 +2881,7 @@ menu.

        -

        Power Meter

        +

        Power Meter

        The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

        The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -2870,11 +2890,11 @@ key and activate after selecting Pressure Plate in the build menu.

        configuration page, but configures parameters relevant to the modded power meter building.

        -

        Steam Engine

        +

        Steam Engine

        The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

        -

        Rationale

        +

        Rationale

        The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -2885,7 +2905,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

        -

        Construction

        +

        Construction

        The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

        @@ -2909,7 +2929,7 @@ short axles that can be built later than both of the engines.

        -

        Operation

        +

        Operation

        In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -2940,7 +2960,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

        -

        Explosions

        +

        Explosions

        The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

        During operation weak parts get gradually worn out, and @@ -2949,7 +2969,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

        -

        Save files

        +

        Save files

        It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -2960,7 +2980,7 @@ being generated.

        -

        Add Spatter

        +

        Add Spatter

        This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index 0b2fbc378..d9021c7cb 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1898,6 +1898,31 @@ Pressing ESC normally returns to the unit screen, but Shift-ESC would exit directly to the main dwarf mode screen. +Search +====== + +The search plugin adds search to the Stocks, Trading and Unit List screens. + +Searching works the same way as the search option in "Move to Depot" does. +You will see the Search option displayed on screen with a hotkey (usually 's'). +Pressing it lets you start typing a query and the relevant list will start +filtering automatically. + +Pressing ENTER, ESC or the arrow keys will return you to browsing the now +filtered list, which still functions as normal. You can clear the filter +by either going back into search mode and backspacing to delete it, or +pressing the "shifted" version of the search hotkey while browsing the +list (e.g. if the hotkey is 's', then hitting 'shift-s' will clear any +filter). + +Leaving any screen automatically clears the filter. + +In the Trade screen, the actual trade will always only act on items that +are actually visible in the list; the same effect applies to the Trade +Value numbers displayed by the screen. Because of this, pressing the 't' +key while search is active clears the search instead of executing the trade. + + gui/liquids =========== From 959831685506a3c92f8af0f6678afa91c95c4aa8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 20:06:33 +0400 Subject: [PATCH 14/24] Add a native pen object for lua with a more checked behavior. --- Lua API.html | 82 +++++--- Lua API.rst | 76 +++++-- library/LuaApi.cpp | 349 ++++++++++++++++++++++++++++--- library/include/LuaTools.h | 6 + library/include/modules/Screen.h | 2 + library/lua/gui.lua | 62 +++--- library/lua/gui/dialogs.lua | 4 +- 7 files changed, 466 insertions(+), 115 deletions(-) diff --git a/Lua API.html b/Lua API.html index a52347104..5dff4b3e4 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1555,37 +1555,12 @@ be feasibly used in the core context.

        Checks if [GRAPHICS:YES] was specified in init.

      • dfhack.screen.paintTile(pen,x,y[,char,tile])

        -

        Paints a tile using given parameters. Pen is a table with following possible fields:

        -
        -
        ch
        -

        Provides the ordinary tile character, as either a 1-character string or a number. -Can be overridden with the char function parameter.

        -
        -
        fg
        -

        Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).

        -
        -
        bg
        -

        Background color for the ordinary tile. Defaults to COLOR_BLACK (0).

        -
        -
        bold
        -

        Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. -Otherwise should be true/false.

        -
        -
        tile
        -

        Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt.

        -
        -
        tile_color = true
        -

        Specifies that the tile should be shaded with fg/bg.

        -
        -
        tile_fg, tile_bg
        -

        If specified, overrides tile_color and supplies shading colors directly.

        -
        -
        +

        Paints a tile using given parameters. See below for a description of pen.

        Returns false if coordinates out of bounds, or other error.

      • dfhack.screen.readTile(x,y)

        Retrieves the contents of the specified tile from the screen buffers. -Returns a pen, or nil if invalid or TrueType.

        +Returns a pen object, or nil if invalid or TrueType.

      • dfhack.screen.paintString(pen,x,y,text)

        Paints the string starting at x,y. Uses the string characters @@ -1610,6 +1585,59 @@ The values can then be used for the tile field of pen structur functions in this section, this may be used at any time.

      +

      The "pen" argument used by functions above may be represented by +a table with the following possible fields:

      +
      +
      +
      ch
      +
      Provides the ordinary tile character, as either a 1-character string or a number. +Can be overridden with the char function parameter.
      +
      fg
      +
      Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).
      +
      bg
      +
      Background color for the ordinary tile. Defaults to COLOR_BLACK (0).
      +
      bold
      +
      Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. +Otherwise should be true/false.
      +
      tile
      +
      Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt.
      +
      tile_color = true
      +
      Specifies that the tile should be shaded with fg/bg.
      +
      tile_fg, tile_bg
      +
      If specified, overrides tile_color and supplies shading colors directly.
      +
      +
      +

      Alternatively, it may be a pre-parsed native object with the following API:

      +
        +
      • dfhack.pen.make(base[,pen_or_fg,bg,bold])

        +

        Creates a new pre-parsed pen by combining its arguments according to the +following rules:

        +
          +
        1. The base argument may be a pen object, a pen table as specified above, +or a single color value. In the single value case, it is split into +fg and bold properties, and others are initialized to 0. +This argument will be converted to a pre-parsed object and returned +if there are no other arguments.
        2. +
        3. If the pen_or_fg argument is specified as a table or object, it +completely replaces the base, and is returned instead of it.
        4. +
        5. Otherwise, the non-nil subset of the optional arguments is used +to update the fg, bg and bold properties of the base. +If the bold flag is nil, but pen_or_fg is a number, bold +is deduced from it like in the simple base case.
        6. +
        +

        This function always returns a new pre-parsed pen, or nil.

        +
      • +
      • dfhack.pen.parse(base[,pen_or_fg,bg,bold])

        +

        Exactly like the above function, but returns base or pen_or_fg +directly if they are already a pre-parsed native object.

        +
      • +
      • pen.property, pen.property = value, pairs(pen)

        +

        Pre-parsed pens support reading and setting their properties, +but don't behave exactly like a simple table would; for instance, +assigning to pen.tile_color also resets pen.tile_fg and +pen.tile_bg to nil.

        +
      • +

      In order to actually be able to paint to the screen, it is necessary to create and register a viewscreen (basically a modal dialog) with the game.

      diff --git a/Lua API.rst b/Lua API.rst index bd712d301..ec48938db 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1403,31 +1403,14 @@ Basic painting functions: * ``dfhack.screen.paintTile(pen,x,y[,char,tile])`` - Paints a tile using given parameters. Pen is a table with following possible fields: - - ``ch`` - Provides the ordinary tile character, as either a 1-character string or a number. - Can be overridden with the ``char`` function parameter. - ``fg`` - Foreground color for the ordinary tile. Defaults to COLOR_GREY (7). - ``bg`` - Background color for the ordinary tile. Defaults to COLOR_BLACK (0). - ``bold`` - Bright/bold text flag. If *nil*, computed based on (fg & 8); fg is masked to 3 bits. - Otherwise should be *true/false*. - ``tile`` - Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt. - ``tile_color = true`` - Specifies that the tile should be shaded with *fg/bg*. - ``tile_fg, tile_bg`` - If specified, overrides *tile_color* and supplies shading colors directly. + Paints a tile using given parameters. See below for a description of pen. Returns *false* if coordinates out of bounds, or other error. * ``dfhack.screen.readTile(x,y)`` Retrieves the contents of the specified tile from the screen buffers. - Returns a pen, or *nil* if invalid or TrueType. + Returns a pen object, or *nil* if invalid or TrueType. * ``dfhack.screen.paintString(pen,x,y,text)`` @@ -1458,6 +1441,61 @@ Basic painting functions: Requests repaint of the screen by setting a flag. Unlike other functions in this section, this may be used at any time. +The "pen" argument used by functions above may be represented by +a table with the following possible fields: + + ``ch`` + Provides the ordinary tile character, as either a 1-character string or a number. + Can be overridden with the ``char`` function parameter. + ``fg`` + Foreground color for the ordinary tile. Defaults to COLOR_GREY (7). + ``bg`` + Background color for the ordinary tile. Defaults to COLOR_BLACK (0). + ``bold`` + Bright/bold text flag. If *nil*, computed based on (fg & 8); fg is masked to 3 bits. + Otherwise should be *true/false*. + ``tile`` + Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt. + ``tile_color = true`` + Specifies that the tile should be shaded with *fg/bg*. + ``tile_fg, tile_bg`` + If specified, overrides *tile_color* and supplies shading colors directly. + +Alternatively, it may be a pre-parsed native object with the following API: + +* ``dfhack.pen.make(base[,pen_or_fg,bg,bold])`` + + Creates a new pre-parsed pen by combining its arguments according to the + following rules: + + 1. The ``base`` argument may be a pen object, a pen table as specified above, + or a single color value. In the single value case, it is split into + ``fg`` and ``bold`` properties, and others are initialized to 0. + This argument will be converted to a pre-parsed object and returned + if there are no other arguments. + + 2. If the ``pen_or_fg`` argument is specified as a table or object, it + completely replaces the base, and is returned instead of it. + + 3. Otherwise, the non-nil subset of the optional arguments is used + to update the ``fg``, ``bg`` and ``bold`` properties of the base. + If the ``bold`` flag is *nil*, but *pen_or_fg* is a number, ``bold`` + is deduced from it like in the simple base case. + + This function always returns a new pre-parsed pen, or *nil*. + +* ``dfhack.pen.parse(base[,pen_or_fg,bg,bold])`` + + Exactly like the above function, but returns ``base`` or ``pen_or_fg`` + directly if they are already a pre-parsed native object. + +* ``pen.property``, ``pen.property = value``, ``pairs(pen)`` + + Pre-parsed pens support reading and setting their properties, + but don't behave exactly like a simple table would; for instance, + assigning to ``pen.tile_color`` also resets ``pen.tile_fg`` and + ``pen.tile_bg`` to *nil*. + In order to actually be able to paint to the screen, it is necessary to create and register a viewscreen (basically a modal dialog) with the game. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index e7424ad50..0151ed404 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -725,6 +725,316 @@ static void OpenMatinfo(lua_State *state) lua_pop(state, 1); } +/************** + * Pen object * + **************/ + +static int DFHACK_PEN_TOKEN = 0; + +void Lua::Push(lua_State *L, const Screen::Pen &info) +{ + if (!info.valid()) + { + lua_pushnil(L); + return; + } + + void *pdata = lua_newuserdata(L, sizeof(Pen)); + + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + lua_setmetatable(L, -2); + + new (pdata) Pen(info); +} + +static Pen *check_pen_native(lua_State *L, int index) +{ + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + + if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2)) + luaL_argerror(L, index, "not a pen object"); + + lua_pop(L, 2); + + return (Pen*)lua_touserdata(L, index); +} + +void Lua::CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil, bool allow_color) +{ + index = lua_absindex(L, index); + + luaL_checkany(L, index); + + if (lua_isnil(L, index)) + { + if (!allow_nil) + luaL_argerror(L, index, "nil pen not allowed"); + + *pen = Pen(0,0,0,-1); + } + else if (lua_isuserdata(L, index)) + { + *pen = *check_pen_native(L, index); + } + else if (allow_color && lua_isnumber(L, index)) + { + *pen = Pen(0, lua_tointeger(L, index)&15, 0); + } + else + { + luaL_checktype(L, index, LUA_TTABLE); + decode_pen(L, *pen, index); + } +} + +static int adjust_pen(lua_State *L, bool no_copy) +{ + lua_settop(L, 4); + + Pen pen; + int iidx = 1; + Lua::CheckPen(L, &pen, 1, true, true); + + if (!lua_isnil(L, 2) || !lua_isnil(L, 3) || !lua_isnil(L, 4)) + { + if (lua_isnumber(L, 2) || lua_isnil(L, 2)) + { + if (!pen.valid()) + pen = Pen(); + + iidx = -1; + + pen.fg = luaL_optint(L, 2, pen.fg) & 15; + pen.bg = luaL_optint(L, 3, pen.bg); + + if (!lua_isnil(L, 4)) + pen.bold = lua_toboolean(L, 4); + else if (!lua_isnil(L, 2)) + { + pen.bold = !!(pen.fg & 8); + pen.fg &= 7; + } + } + else + { + iidx = 2; + Lua::CheckPen(L, &pen, 2, false, false); + } + } + + if (no_copy && iidx > 0 && lua_isuserdata(L, iidx)) + lua_pushvalue(L, iidx); + else + Lua::Push(L, pen); + + return 1; +} + +static int dfhack_pen_parse(lua_State *L) +{ + return adjust_pen(L, true); +} + +static int dfhack_pen_make(lua_State *L) +{ + return adjust_pen(L, false); +} + +static void make_pen_table(lua_State *L, Pen &pen) +{ + if (!pen.valid()) + luaL_error(L, "invalid pen state"); + else + { + lua_newtable(L); + lua_pushinteger(L, (unsigned char)pen.ch); lua_setfield(L, -2, "ch"); + lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); + lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); + lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); + + if (pen.tile) + { + lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); + } + + switch (pen.tile_mode) { + case Pen::CharColor: + lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); + break; + case Pen::TileColor: + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); + break; + default: + lua_pushboolean(L, false); lua_setfield(L, -2, "tile_color"); + break; + } + } +} + +static void get_pen_mirror(lua_State *L, int idx) +{ + lua_getuservalue(L, idx); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + + Pen pen; + Lua::CheckPen(L, &pen, idx, false, false); + make_pen_table(L, pen); + + lua_dup(L); + lua_setuservalue(L, idx); + } +} + +static int dfhack_pen_index(lua_State *L) +{ + lua_settop(L, 2); + luaL_checktype(L, 1, LUA_TUSERDATA); + + // check metatable + if (!lua_getmetatable(L, 1)) + luaL_argerror(L, 1, "must be a pen"); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) + return 1; + + // otherwise read from the mirror table, creating it if necessary + lua_settop(L, 2); + get_pen_mirror(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + return 1; +} + +static int pen_pnext(lua_State *L) +{ + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, lua_upvalueindex(1))) + return 2; + lua_pushnil(L); + return 1; +} + +static int dfhack_pen_pairs(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + get_pen_mirror(L, 1); + lua_pushcclosure(L, pen_pnext, 1); + lua_pushnil(L); + lua_pushnil(L); + return 3; +} + +const char *const pen_fields[] = { + "ch", "fg", "bold", "bg", "tile", "tile_color", "tile_fg", "tile_bg", NULL +}; + +static int dfhack_pen_newindex(lua_State *L) +{ + lua_settop(L, 3); + luaL_checktype(L, 1, LUA_TUSERDATA); + int id = luaL_checkoption(L, 2, NULL, pen_fields); + int arg = 0; + Pen &pen = *check_pen_native(L, 1); + bool wipe_tile = false, wipe_tc = false; + + switch (id) { + case 0: + if (lua_type(L, 3) != LUA_TNUMBER) + arg = (unsigned char)*luaL_checkstring(L, 3); + else + arg = luaL_checkint(L, 3); + pen.ch = arg; + lua_pushinteger(L, (unsigned char)pen.ch); + break; + case 1: + pen.fg = luaL_checkint(L, 3) & 15; + lua_pushinteger(L, pen.fg); + break; + case 2: + pen.bold = lua_toboolean(L, 3); + lua_pushboolean(L, pen.bold); + break; + case 3: + pen.bg = luaL_checkint(L, 3) & 15; + lua_pushinteger(L, pen.bg); + break; + case 4: + arg = lua_isnil(L, 3) ? 0 : luaL_checkint(L, 3); + if (arg < 0) + luaL_argerror(L, 3, "invalid tile index"); + pen.tile = arg; + if (pen.tile) + lua_pushinteger(L, pen.tile); + else + lua_pushnil(L); + break; + case 5: + wipe_tile = (pen.tile_mode == Pen::TileColor); + pen.tile_mode = lua_toboolean(L, 3) ? Pen::CharColor : Pen::AsIs; + lua_pushboolean(L, pen.tile_mode == Pen::CharColor); + break; + case 6: + if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_bg = 0; } + pen.tile_fg = luaL_checkint(L, 3) & 15; + pen.tile_mode = Pen::TileColor; + lua_pushinteger(L, pen.tile_fg); + break; + case 7: + if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_fg = 7; } + pen.tile_bg = luaL_checkint(L, 3) & 15; + pen.tile_mode = Pen::TileColor; + lua_pushinteger(L, pen.tile_bg); + break; + } + + lua_getuservalue(L, 1); + + if (!lua_isnil(L, -1)) + { + lua_remove(L, 3); + lua_insert(L, 2); + lua_rawset(L, 2); + + if (wipe_tc) { + lua_pushnil(L); lua_setfield(L, 2, "tile_color"); + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, 2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, 2, "tile_bg"); + } + if (wipe_tile) { + lua_pushnil(L); lua_setfield(L, 2, "tile_fg"); + lua_pushnil(L); lua_setfield(L, 2, "tile_bg"); + } + } + + return 0; +} + +static const luaL_Reg dfhack_pen_funcs[] = { + { "parse", dfhack_pen_parse }, + { "make", dfhack_pen_make }, + { "__index", dfhack_pen_index }, + { "__pairs", dfhack_pen_pairs }, + { "__newindex", dfhack_pen_newindex }, + { NULL, NULL } +}; + +static void OpenPen(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "pen"); + + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + + luaL_setfuncs(state, dfhack_pen_funcs, 0); + + lua_pop(state, 1); +} + /************************ * Wrappers for C++ API * ************************/ @@ -1251,7 +1561,7 @@ static int screen_getWindowSize(lua_State *L) static int screen_paintTile(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) @@ -1272,44 +1582,14 @@ static int screen_readTile(lua_State *L) int x = luaL_checkint(L, 1); int y = luaL_checkint(L, 2); Pen pen = Screen::readTile(x, y); - - if (!pen.valid()) - { - lua_pushnil(L); - } - else - { - lua_newtable(L); - lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch"); - lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); - lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); - lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); - - if (pen.tile) - { - lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); - - switch (pen.tile_mode) { - case Pen::CharColor: - lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); - break; - case Pen::TileColor: - lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); - lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); - break; - default: - break; - } - } - } - + Lua::Push(L, pen); return 1; } static int screen_paintString(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); const char *text = luaL_checkstring(L, 4); @@ -1320,7 +1600,7 @@ static int screen_paintString(lua_State *L) static int screen_fillRect(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x1 = luaL_checkint(L, 2); int y1 = luaL_checkint(L, 3); int x2 = luaL_checkint(L, 4); @@ -1720,6 +2000,7 @@ void OpenDFHackApi(lua_State *state) { OpenPersistent(state); OpenMatinfo(state); + OpenPen(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index ec4917972..655d069d3 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -41,6 +41,9 @@ namespace DFHack { namespace Units { struct NoblePosition; } + namespace Screen { + struct Pen; + }; } namespace DFHack {namespace Lua { @@ -285,6 +288,7 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); void Push(lua_State *state, const Units::NoblePosition &pos); DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info); + DFHACK_EXPORT void Push(lua_State *state, const Screen::Pen &info); template inline void Push(lua_State *state, T *ptr) { PushDFObject(state, ptr); } @@ -315,6 +319,8 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); + DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true); + DFHACK_EXPORT bool IsCoreContext(lua_State *state); namespace Event { diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index ccd7f2f8d..d8b5774e9 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -76,6 +76,8 @@ namespace DFHack bool valid() const { return tile >= 0; } bool empty() const { return ch == 0 && tile == 0; } + // NOTE: LuaApi.cpp assumes this struct is plain data and has empty destructor + Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false) : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 15d03742f..cfb058f9d 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -6,7 +6,9 @@ local dscreen = dfhack.screen USE_GRAPHICS = dscreen.inGraphicsMode() -CLEAR_PEN = {ch=32,fg=0,bg=0} +local to_pen = dfhack.pen.parse + +CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} function simulateInput(screen,...) local keys = {} @@ -116,16 +118,6 @@ function blink_visible(delay) return math.floor(dfhack.getTickCount()/delay) % 2 == 0 end -function to_pen(default, pen, bg, bold) - if pen == nil then - return default or {} - elseif type(pen) ~= 'table' then - return {fg=pen,bg=bg,bold=bold} - else - return pen - end -end - function getKeyDisplay(code) if type(code) == 'string' then code = df.interface_key[code] @@ -215,7 +207,8 @@ Painter = defclass(Painter, ViewRect) function Painter:init(args) self.x = self.x1 self.y = self.y1 - self.cur_pen = to_pen(nil, args.pen or COLOR_GREY) + self.cur_pen = to_pen(args.pen or COLOR_GREY) + self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN) end function Painter.new(rect, pen) @@ -241,6 +234,7 @@ end function Painter:viewport(x,y,w,h) local vp = ViewRect.viewport(x,y,w,h) vp.cur_pen = self.cur_pen + vp.cur_key_pen = self.cur_key_pen return mkinstance(Painter, vp):seek(0,0) end @@ -280,10 +274,12 @@ function Painter:pen(pen,...) end function Painter:color(fg,bold,bg) - self.cur_pen = copyall(self.cur_pen) - self.cur_pen.fg = fg - self.cur_pen.bold = bold - if bg then self.cur_pen.bg = bg end + self.cur_pen = to_pen(self.cur_pen, fg, bg, bold) + return self +end + +function Painter:key_pen(pen,...) + self.cur_key_pen = to_pen(self.cur_key_pen, pen, ...) return self end @@ -339,10 +335,10 @@ function Painter:string(text,pen,...) return self:advance(#text, nil) end -function Painter:key(code,pen,bg,...) +function Painter:key(code,pen,...) return self:string( getKeyDisplay(code), - pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ... + to_pen(self.cur_key_pen, pen, ...) ) end @@ -557,28 +553,28 @@ end -- Plain grey-colored frame. GREY_FRAME = { - frame_pen = { ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, - title_pen = { fg = COLOR_BLACK, bg = COLOR_WHITE }, - signature_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, + frame_pen = to_pen{ ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, + title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_WHITE }, + signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY }, } -- The usual boundary used by the DF screens. Often has fancy pattern in tilesets. BOUNDARY_FRAME = { - frame_pen = { ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK }, - title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, - signature_pen = { fg = COLOR_BLACK, bg = COLOR_DARKGREY }, + frame_pen = to_pen{ ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK }, + title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY }, + signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_DARKGREY }, } GREY_LINE_FRAME = { - frame_pen = { ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK }, - h_frame_pen = { ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK }, - v_frame_pen = { ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK }, - lt_frame_pen = { ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK }, - lb_frame_pen = { ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK }, - rt_frame_pen = { ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK }, - rb_frame_pen = { ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK }, - title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, - signature_pen = { fg = COLOR_DARKGREY, bg = COLOR_BLACK }, + frame_pen = to_pen{ ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK }, + h_frame_pen = to_pen{ ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK }, + v_frame_pen = to_pen{ ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK }, + lt_frame_pen = to_pen{ ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK }, + lb_frame_pen = to_pen{ ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK }, + rt_frame_pen = to_pen{ ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK }, + rb_frame_pen = to_pen{ ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK }, + title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY }, + signature_pen = to_pen{ fg = COLOR_DARKGREY, bg = COLOR_BLACK }, } function paint_frame(x1,y1,x2,y2,style,title) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 5811e94e6..0a79b4c3e 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -160,8 +160,8 @@ function ListBox:preinit(info) end function ListBox:init(info) - local spen = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false) - local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) + local spen = dfhack.pen.parse(COLOR_CYAN, self.select_pen, nil, false) + local cpen = dfhack.pen.parse(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) local list_widget = widgets.List if self.with_filter then From bd8c59462c897207a6f386a73d93bb0f7fe33182 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 4 Nov 2012 17:06:32 +0400 Subject: [PATCH 15/24] Add documentation for the core lua gui library stuff. --- Lua API.html | 833 +++++++++++++++++++++++++++++++++++- Lua API.rst | 782 ++++++++++++++++++++++++++++++++- library/lua/gui/widgets.lua | 5 +- library/lua/utils.lua | 1 + 4 files changed, 1603 insertions(+), 18 deletions(-) diff --git a/Lua API.html b/Lua API.html index 5dff4b3e4..226afa98a 100644 --- a/Lua API.html +++ b/Lua API.html @@ -377,12 +377,34 @@ ul.auto-toc {
    • class
  • -
  • Plugins

    The current version of DFHack has extensive support for @@ -1584,6 +1606,10 @@ The values can then be used for the tile field of pen structur

    Requests repaint of the screen by setting a flag. Unlike other functions in this section, this may be used at any time.

  • +
  • dfhack.screen.getKeyDisplay(key)

    +

    Returns the string that should be used to represent the given +logical keybinding on the screen in texts like "press Key to ...".

    +
  • The "pen" argument used by functions above may be represented by a table with the following possible fields:

    @@ -1889,6 +1915,19 @@ SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED

  • Functions already described above

    safecall, qerror, mkmodule, reload

  • +
  • Miscellaneous constants

    + +++ + + + + + + +
    NEWLINE, COMMA, PERIOD:
     evaluate to the relevant character strings.
    DEFAULT_NIL:is an unspecified unique token used by the class module below.
    +
  • printall(obj)

    If the argument is a lua table or DF object reference, prints all fields.

  • @@ -1988,11 +2027,25 @@ are converted to 1-based lua sequences.

    as a guide to which values should be skipped as uninteresting. The force argument makes it always return a non-nil value.

    +
  • utils.parse_bitfield_int(value, type_ref)

    +

    Given an int value, and a bitfield type in the df tree, +it returns a lua table mapping the enabled bit keys to true, +unless value is 0, in which case it returns nil.

    +
  • +
  • utils.list_bitfield_flags(bitfield[, list])

    +

    Adds all enabled bitfield keys to list or a newly-allocated +empty sequence, and returns it. The bitfield argument may +be nil.

    +
  • utils.sort_vector(vector,field,cmpfun)

    Sorts a native vector or lua sequence using the comparator function. If field is not nil, applies the comparator to the field instead of the whole object.

  • +
  • utils.linear_index(vector,key[,field])

    +

    Searches for key in the vector, and returns index, found_value, +or nil if none found.

    +
  • utils.binsearch(vector,key,field,cmpfun,min,max)

    Does a binary search in a native vector or lua sequence for key, using cmpfun and field like sort_vector. @@ -2021,6 +2074,19 @@ utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')

  • utils.erase_sorted(vector,item,field,cmpfun)

    Exactly like erase_sorted_key, but if field is specified, takes the key from item[field].

  • +
  • utils.call_with_string(obj,methodname,...)

    +

    Allocates a temporary string object, calls obj:method(tmp,...), and +returns the value written into the temporary after deleting it.

    +
  • +
  • utils.getBuildingName(building)

    +

    Returns the string description of the given building.

    +
  • +
  • utils.getBuildingCenter(building)

    +

    Returns an x/y/z table pointing at the building center.

    +
  • +
  • utils.split_string(string, delimiter)

    +

    Splits the string by the given delimiter, and returns a sequence of results.

    +
  • utils.prompt_yes_no(prompt, default)

    Presents a yes/no prompt to the user. If default is not nil, allows just pressing Enter to submit the default choice. @@ -2072,19 +2138,31 @@ calling superclass methods.

    from fields in the table used as the constructor argument. If omitted, they are initialized with the default values specified in this declaration.

    If the default value should be nil, use ATTRS { foo = DEFAULT_NIL }.

    +

    Declaring an attribute is mostly the same as defining your init method like this:

    +
    +function Class.init(args)
    +    self.attr1 = args.attr1 or default1
    +    self.attr2 = args.attr2 or default2
    +    ...
    +end
    +
    +

    The main difference is that attributes are processed as a separate +initialization step, before any init methods are called. They +also make the directy relation between instance fields and constructor +arguments more explicit.

  • new_obj = Class{ foo = arg, bar = arg, ... }

    Calling the class as a function creates and initializes a new instance. Initialization happens in this order:

    1. An empty instance table is created, and its metatable set.
    2. -
    3. The preinit method is called via invoke_before (see below) -with the table used as argument to the class. This method is intended +
    4. The preinit methods are called via invoke_before (see below) +with the table used as argument to the class. These methods are intended for validating and tweaking that argument table.
    5. Declared ATTRS are initialized from the argument table or their default values.
    6. -
    7. The init method is called via invoke_after with the argument table. +
    8. The init methods are called via invoke_after with the argument table. This is the main constructor method.
    9. -
    10. The postinit method is called via invoke_after with the argument table. +
    11. The postinit methods are called via invoke_after with the argument table. Place code that should be called after the object is fully constructed here.
  • @@ -2099,6 +2177,12 @@ Place code that should be called after the object is fully constructed here. +
  • instance:cb_getfield(field_name)

    +

    Returns a closure that returns the specified field of the object when called.

    +
  • +
  • instance:cb_setfield(field_name)

    +

    Returns a closure that sets the specified field to its argument when called.

    +
  • instance:invoke_before(method_name, args...)

    Navigates the inheritance chain of the instance starting from the most specific class, and invokes the specified method with the arguments if it is defined in @@ -2123,15 +2207,740 @@ library itself uses them for constructors.

    To avoid confusion, these methods cannot be redefined.

    +
    +

    In-game UI Library

    +

    A number of lua modules with names starting with gui are dedicated +to wrapping the natives of the dfhack.screen module in a way that +is easy to use. This allows relatively easily and naturally creating +dialogs that integrate in the main game UI window.

    +

    These modules make extensive use of the class module, and define +things ranging from the basic Painter, View and Screen +classes, to fully functional predefined dialogs.

    +
    +

    gui

    +

    This module defines the most important classes and functions for +implementing interfaces. This documents those of them that are +considered stable.

    +
    +

    Misc

    +
      +
    • USE_GRAPHICS

      +

      Contains the value of dfhack.screen.inGraphicsMode(), which cannot be +changed without restarting the game and thus is constant during the session.

      +
    • +
    • CLEAR_PEN

      +

      The black pen used to clear the screen.

      +
    • +
    • simulateInput(screen, keys...)

      +

      This function wraps an undocumented native function that passes a set of +keycodes to a screen, and is the official way to do that.

      +

      Every argument after the initial screen may be nil, a numeric keycode, +a string keycode, a sequence of numeric or string keycodes, or a mapping +of keycodes to true or false. For instance, it is possible to use the +table passed as argument to onInput.

      +
    • +
    • mkdims_xy(x1,y1,x2,y2)

      +

      Returns a table containing the arguments as fields, and also width and +height that contains the rectangle dimensions.

      +
    • +
    • mkdims_wh(x1,y1,width,height)

      +

      Returns the same kind of table as mkdims_xy, only this time it computes +x2 and y2.

      +
    • +
    • is_in_rect(rect,x,y)

      +

      Checks if the given point is within a rectangle, represented by a table produced +by one of the mkdims functions.

      +
    • +
    • blink_visible(delay)

      +

      Returns true or false, with the value switching to the opposite every delay +msec. This is intended for rendering blinking interface objects.

      +
    • +
    • getKeyDisplay(keycode)

      +

      Wraps dfhack.screen.getKeyDisplay in order to allow using strings for the keycode argument.

      +
    • +
    +
    +
    +

    ViewRect class

    +

    This class represents an on-screen rectangle with an associated independent +clip area rectangle. It is the base of the Painter class, and is used by +Views to track their client area.

    +
      +
    • ViewRect{ rect = ..., clip_rect = ..., view_rect = ..., clip_view = ... }

      +

      The constructor has the following arguments:

      + +++ + + + + + + + + + +
      rect:The mkdims rectangle in screen coordinates of the logical viewport. +Defaults to the whole screen.
      clip_rect:The clip rectangle in screen coordinates. Defaults to rect.
      view_rect:A ViewRect object to copy from; overrides both rect and clip_rect.
      clip_view:A ViewRect object to intersect the specified clip area with.
      +
    • +
    • rect:isDefunct()

      +

      Returns true if the clip area is empty, i.e. no painting is possible.

      +
    • +
    • rect:inClipGlobalXY(x,y)

      +

      Checks if these global coordinates are within the clip rectangle.

      +
    • +
    • rect:inClipLocalXY(x,y)

      +

      Checks if these coordinates (specified relative to x1,y1) are within the clip rectangle.

      +
    • +
    • rect:localXY(x,y)

      +

      Converts a pair of global coordinates to local; returns x_local,y_local.

      +
    • +
    • rect:globalXY(x,y)

      +

      Converts a pair of local coordinates to global; returns x_global,y_global.

      +
    • +
    • rect:viewport(x,y,w,h) or rect:viewport(subrect)

      +

      Returns a ViewRect representing a sub-rectangle of the current one. +The arguments are specified in local coordinates; the subrect +argument must be a mkdims table. The returned object consists of +the exact specified rectangle, and a clip area produced by intersecting +it with the clip area of the original object.

      +
    • +
    +
    +
    +

    Painter class

    +

    The painting natives in dfhack.screen apply to the whole screen, are +completely stateless and don't implement clipping.

    +

    The Painter class inherits from ViewRect to provide clipping and local +coordinates, and tracks current cursor position and current pen.

    +
      +
    • Painter{ ..., pen = ..., key_pen = ... }

      +

      In addition to ViewRect arguments, Painter accepts a suggestion of +the initial value for the main pen, and the keybinding pen. They +default to COLOR_GREY and COLOR_LIGHTGREEN otherwise.

      +

      There are also some convenience functions that wrap this constructor:

      +
        +
      • Painter.new(rect,pen)
      • +
      • Painter.new_view(view_rect,pen)
      • +
      • Painter.new_xy(x1,y1,x2,y2,pen)
      • +
      • Painter.new_wh(x1,y1,width,height,pen)
      • +
      +
    • +
    • painter:isValidPos()

      +

      Checks if the current cursor position is within the clip area.

      +
    • +
    • painter:viewport(x,y,w,h)

      +

      Like the superclass method, but returns a Painter object.

      +
    • +
    • painter:cursor()

      +

      Returns the current cursor x,y in local coordinates.

      +
    • +
    • painter:seek(x,y)

      +

      Sets the current cursor position, and returns self. +Either of the arguments may be nil to keep the current value.

      +
    • +
    • painter:advance(dx,dy)

      +

      Adds the given offsets to the cursor position, and returns self. +Either of the arguments may be nil to keep the current value.

      +
    • +
    • painter:newline([dx])

      +

      Advances the cursor to the start of the next line plus the given x offset, and returns self.

      +
    • +
    • painter:pen(...)

      +

      Sets the current pen to dfhack.pen.parse(old_pen,...), and returns self.

      +
    • +
    • painter:key_pen(...)

      +

      Sets the current keybinding pen to dfhack.pen.parse(old_pen,...), and returns self.

      +
    • +
    • painter:clear()

      +

      Fills the whole clip rectangle with CLEAR_PEN, and returns self.

      +
    • +
    • painter:fill(x1,y1,x2,y2[,...]) or painter:fill(rect[,...])

      +

      Fills the specified local coordinate rectangle with dfhack.pen.parse(cur_pen,...), +and returns self.

      +
    • +
    • painter:char([char[, ...]])

      +

      Paints one character using char and dfhack.pen.parse(cur_pen,...); returns self. +The char argument, if not nil, is used to override the ch property of the pen.

      +
    • +
    • painter:tile([char, tile[, ...]])

      +

      Like above, but also allows overriding the tile property on ad-hoc basis.

      +
    • +
    • painter:string(text[, ...])

      +

      Paints the string with dfhack.pen.parse(cur_pen,...); returns self.

      +
    • +
    • painter:key(keycode[, ...])

      +

      Paints the description of the keycode using dfhack.pen.parse(cur_key_pen,...); returns self.

      +
    • +
    +

    As noted above, all painting methods return self, in order to allow chaining them like this:

    +
    +painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')...
    +
    +
    +
    +

    View class

    +

    This class is the common abstract base of both the stand-alone screens +and common widgets to be used inside them. It defines the basic layout, +rendering and event handling framework.

    +

    The class defines the following attributes:

    + +++ + + + + + + + +
    visible:Specifies that the view should be painted.
    active:Specifies that the view should receive events, if also visible.
    view_id:Specifies an identifier to easily identify the view among subviews. +This is reserved for implementation of top-level views, and should +not be used by widgets for their internal subviews.
    +

    It also always has the following fields:

    + +++ + + + +
    subviews:Contains a table of all subviews. The sequence part of the +table is used for iteration. In addition, subviews are also +indexed under their view_id, if any; see addviews() below.
    +

    These fields are computed by the layout process:

    + +++ + + + + + + + + +
    frame_parent_rect:
     The ViewRect represeting the client area of the parent view.
    frame_rect:The mkdims rect of the outer frame in parent-local coordinates.
    frame_body:The ViewRect representing the body part of the View's own frame.
    +

    The class has the following methods:

    +
      +
    • view:addviews(list)

      +

      Adds the views in the list to the subviews sequence. If any of the views +in the list have view_id attributes that don't conflict with existing keys +in subviews, also stores them under the string keys. Finally, copies any +non-conflicting string keys from the subviews tables of the listed views.

      +

      Thus, doing something like this:

      +
      +self:addviews{
      +    Panel{
      +        view_id = 'panel',
      +        subviews = {
      +            Label{ view_id = 'label' }
      +        }
      +    }
      +}
      +
      +

      Would make the label accessible as both self.subviews.label and +self.subviews.panel.subviews.label.

      +
    • +
    • view:getWindowSize()

      +

      Returns the dimensions of the frame_body rectangle.

      +
    • +
    • view:getMousePos()

      +

      Returns the mouse x,y in coordinates local to the frame_body +rectangle if it is within its clip area, or nothing otherwise.

      +
    • +
    • view:updateLayout([parent_rect])

      +

      Recomputes layout of the view and its subviews. If no argument is +given, re-uses the previous parent rect. The process goes as follows:

      +
        +
      1. Calls preUpdateLayout(parent_rect) via invoke_before.
      2. +
      3. Uses computeFrame(parent_rect) to compute the desired frame.
      4. +
      5. Calls postComputeFrame(frame_body) via invoke_after.
      6. +
      7. Calls updateSubviewLayout(frame_body) to update children.
      8. +
      9. Calls postUpdateLayout(frame_body) via invoke_after.
      10. +
      +
    • +
    • view:computeFrame(parent_rect) (for overriding)

      +

      Called by updateLayout in order to compute the frame rectangle(s). +Should return the mkdims rectangle for the outer frame, and optionally +also for the body frame. If only one rectangle is returned, it is used +for both frames, and the margin becomes zero.

      +
    • +
    • view:updateSubviewLayout(frame_body)

      +

      Calls updateLayout on all children.

      +
    • +
    • view:render(painter)

      +

      Given the parent's painter, renders the view via the following process:

      +
        +
      1. Calls onRenderFrame(painter, frame_rect) to paint the outer frame.
      2. +
      3. Creates a new painter using the frame_body rect.
      4. +
      5. Calls onRenderBody(new_painter) to paint the client area.
      6. +
      7. Calls renderSubviews(new_painter) to paint visible children.
      8. +
      +
    • +
    • view:renderSubviews(painter)

      +

      Calls render on all visible subviews in the order they +appear in the subviews sequence.

      +
    • +
    • view:onRenderFrame(painter, rect) (for overriding)

      +

      Called by render to paint the outer frame; by default does nothing.

      +
    • +
    • view:onRenderBody(painter) (for overriding)

      +

      Called by render to paint the client area; by default does nothing.

      +
    • +
    • view:onInput(keys) (for overriding)

      +

      Override this to handle events. By default directly calls inputToSubviews. +Return a true value from this method to signal that the event has been handled +and should not be passed on to more views.

      +
    • +
    • view:inputToSubviews(keys)

      +

      Calls onInput on all visible active subviews, iterating the subviews +sequence in reverse order, so that topmost subviews get events first. +Returns true if any of the subviews handled the event.

      +
    • +
    +
    +
    +

    Screen class

    +

    This is a View subclass intended for use as a stand-alone dialog or screen. +It adds the following methods:

    +
      +
    • screen:isShown()

      +

      Returns true if the screen is currently in the game engine's display stack.

      +
    • +
    • screen:isDismissed()

      +

      Returns true if the screen is dismissed.

      +
    • +
    • screen:isActive()

      +

      Returns true if the screen is shown and not dismissed.

      +
    • +
    • screen:invalidate()

      +

      Requests a repaint. Note that currently using it is not necessary, because +repaints are constantly requested automatically, due to issues with native +screens happening otherwise.

      +
    • +
    • screen:renderParent()

      +

      Asks the parent native screen to render itself, or clears the screen if impossible.

      +
    • +
    • screen:sendInputToParent(...)

      +

      Uses simulateInput to send keypresses to the native parent screen.

      +
    • +
    • screen:show([parent])

      +

      Adds the screen to the display stack with the given screen as the parent; +if parent is not specified, places this one one topmost. Before calling +dfhack.screen.show, calls self:onAboutToShow(parent).

      +
    • +
    • screen:onAboutToShow(parent) (for overriding)

      +

      Called when dfhack.screen.show is about to be called.

      +
    • +
    • screen:onShow()

      +

      Called by dfhack.screen.show once the screen is successfully shown.

      +
    • +
    • screen:dismiss()

      +

      Dismisses the screen. A dismissed screen does not receive any more +events or paint requests, but may remain in the display stack for +a short time until the game removes it.

      +
    • +
    • screen:onDismiss() (for overriding)

      +

      Called by dfhack.screen.dismiss().

      +
    • +
    • screen:onDestroy() (for overriding)

      +

      Called by the native code when the screen is fully destroyed and removed +from the display stack. Place code that absolutely must be called whenever +the screen is removed by any means here.

      +
    • +
    • screen:onResize, screen:onRender

      +

      Defined as callbacks for native code.

      +
    • +
    +
    +
    +

    FramedScreen class

    +

    A Screen subclass that paints a visible frame around its body. +Most dialogs should inherit from this class.

    +

    A framed screen has the following attributes:

    + +++ + + + + + + + + + + + + + + +
    frame_style:A table that defines a set of pens to draw various parts of the frame.
    frame_title:A string to display in the middle of the top of the frame.
    frame_width:Desired width of the client area. If nil, the screen will occupy the whole width.
    frame_height:Likewise, for height.
    frame_inset:The gap between the frame and the client area. Defaults to 0.
    frame_background:
     The pen to fill in the frame with. Defaults to CLEAR_PEN.
    +

    There are the following predefined frame style tables:

    +
      +
    • GREY_FRAME

      +

      A plain grey-colored frame.

      +
    • +
    • BOUNDARY_FRAME

      +

      The same frame as used by the usual full-screen DF views, like dwarfmode.

      +
    • +
    • GREY_LINE_FRAME

      +

      A frame consisting of grey lines, similar to the one used by titan announcements.

      +
    • +
    +
    +
    +
    +

    gui.widgets

    +

    This module implements some basic widgets based on the View infrastructure.

    +
    +

    Widget class

    +

    Base of all the widgets. Inherits from View and has the following attributes:

    +
      +
    • frame = {...}

      +

      Specifies the constraints on the outer frame of the widget. +If omitted, the widget will occupy the whole parent rectangle.

      +

      The frame is specified as a table with the following possible fields:

      + +++ + + + + + + + + + + + + + + + + + +
      l:gap between the left edges of the frame and the parent.
      t:gap between the top edges of the frame and the parent.
      r:gap between the right edges of the frame and the parent.
      b:gap between the bottom edges of the frame and the parent.
      w:maximum width of the frame.
      h:maximum heigth of the frame.
      xalign:X alignment of the frame.
      yalign:Y alignment of the frame.
      +

      First the l,t,r,b fields restrict the available area for +placing the frame. If w and h are not specified or +larger then the computed area, it becomes the frame. Otherwise +the smaller frame is placed within the are based on the +xalign/yalign fields. If the align hints are omitted, they +are assumed to be 0, 1, or 0.5 based on which of the l/r/t/b +fields are set.

      +
    • +
    • frame_inset = {...}

      +

      Specifies the gap between the outer frame, and the client area. +The attribute may be a simple integer value to specify a uniform +inset, or a table with the following fields:

      + +++ + + + + + + + + + + + + + +
      l:left margin.
      t:top margin.
      r:right margin.
      b:bottom margin.
      x:left/right margin, if l and/or r are omitted.
      y:top/bottom margin, if t and/or b are omitted.
      +
    • +
    • frame_background = pen

      +

      The pen to fill the outer frame with. Defaults to no fill.

      +
    • +
    +
    +
    +

    Panel class

    +

    Inherits from Widget, and intended for grouping a number of subviews.

    +

    Has attributes:

    +
      +
    • subviews = {}

      +

      Used to initialize the subview list in the constructor.

      +
    • +
    • on_render = function(painter)

      +

      Called from onRenderBody.

      +
    • +
    +
    +
    +

    Pages class

    +

    Subclass of Panel; keeps exactly one child visible.

    +
      +
    • Pages{ ..., selected = ... }

      +

      Specifies which child to select initially; defaults to the first one.

      +
    • +
    • pages:getSelected()

      +

      Returns the selected index, child.

      +
    • +
    • pages:setSelected(index)

      +

      Selects the specified child, hiding the previous selected one. +It is permitted to use the subview object, or its view_id as index.

      +
    • +
    +
    +
    +

    EditField class

    +

    Subclass of Widget; implements a simple edit field.

    +

    Attributes:

    + +++ + + + + + + + + + + + +
    text:The current contents of the field.
    text_pen:The pen to draw the text with.
    on_char:Input validation callback; used as on_char(new_char,text). +If it returns false, the character is ignored.
    on_change:Change notification callback; used as on_change(new_text,old_text).
    on_submit:Enter key callback; if set the field will handle the key and call on_submit(text).
    +
    +
    +

    Label class

    +

    This Widget subclass implements flowing semi-static text.

    +

    It has the following attributes:

    + +++ + + + + + + + + + + + + + +
    text_pen:Specifies the pen for active text.
    text_dpen:Specifies the pen for disabled text.
    disabled:Boolean or a callback; if true, the label is disabled.
    enabled:Boolean or a callback; if false, the label is disabled.
    auto_height:Sets self.frame.h from the text height.
    auto_width:Sets self.frame.w from the text width.
    +

    The text itself is represented as a complex structure, and passed +to the object via the text argument of the constructor, or via +the setText method, as one of:

    +
      +
    • A simple string, possibly containing newlines.
    • +
    • A sequence of tokens.
    • +
    +

    Every token in the sequence in turn may be either a string, possibly +containing newlines, or a table with the following possible fields:

    +
      +
    • token.text = ...

      +

      Specifies the main text content of a token, and may be a string, or +a callback returning a string.

      +
    • +
    • token.gap = ...

      +

      Specifies the number of character positions to advance on the line +before rendering the token.

      +
    • +
    • token.tile = pen

      +

      Specifies a pen to paint as one tile before the main part of the token.

      +
    • +
    • token.key = '...'

      +

      Specifies the keycode associated with the token. The string description +of the key binding is added to the text content of the token.

      +
    • +
    • token.key_sep = '...'

      +

      Specifies the separator to place between the keybinding label produced +by token.key, and the main text of the token. If the separator is +'()', the token is formatted as text..' ('..binding..')'. Otherwise +it is simply binding..sep..text.

      +
    • +
    • token.enabled, token.disabled

      +

      Same as the attributes of the label itself, but applies only to the token.

      +
    • +
    • token.pen, token.dpen

      +

      Specify the pen and disabled pen to be used for the token's text. +The field may be either the pen itself, or a callback that returns it.

      +
    • +
    • token.on_activate

      +

      If this field is not nil, and token.key is set, the token will actually +respond to that key binding unless disabled, and call this callback. Eventually +this may be extended with mouse click support.

      +
    • +
    • token.id

      +

      Specifies a unique identifier for the token.

      +
    • +
    • token.line, token.x1, token.x2

      +

      Reserved for internal use.

      +
    • +
    +

    The Label widget implements the following methods:

    +
      +
    • label:setText(new_text)

      +

      Replaces the text currently contained in the widget.

      +
    • +
    • label:itemById(id)

      +

      Finds a token by its id field.

      +
    • +
    • label:getTextHeight()

      +

      Computes the height of the text.

      +
    • +
    • label:getTextWidth()

      +

      Computes the width of the text.

      +
    • +
    +
    +
    +

    List class

    +

    The List widget implements a simple list with paging.

    +

    It has the following attributes:

    + +++ + + + + + + + + + + + + + + + + + + + +
    text_pen:Specifies the pen for deselected list entries.
    cursor_pen:Specifies the pen for the selected entry.
    inactive_pen:If specified, used for the cursor when the widget is not active.
    icon_pen:Default pen for icons.
    on_select:Selection change callback; called as on_select(index,choice).
    on_submit:Enter key callback; if specified, the list reacts to the key +and calls it as on_submit(index,choice).
    row_height:Height of every row in text lines.
    icon_width:If not nil, the specified number of character columns +are reserved to the left of the list item for the icons.
    scroll_keys:Specifies which keys the list should react to as a table.
    +

    Every list item may be specified either as a string, or as a lua table +with the following fields:

    + +++ + + + + + + + + + + + + + +
    text:Specifies the label text in the same format as the Label text.
    caption, [1]:Deprecated legacy aliases for text.
    text_*:Reserved for internal use.
    key:Specifies a keybinding that acts as a shortcut for the specified item.
    icon:Specifies an icon string, or a pen to paint a single character. May be a callback.
    icon_pen:When the icon is a string, used to paint it.
    +

    The list supports the following methods:

    +
      +
    • List{ ..., choices = ..., selected = ... }

      +

      Same as calling setChoices after construction.

      +
    • +
    • list:setChoices(choices[, selected])

      +

      Replaces the list of choices, possibly also setting the currently selected index.

      +
    • +
    • list:setSelected(selected)

      +

      Sets the currently selected index. Returns the index after validation.

      +
    • +
    • list:getChoices()

      +

      Returns the list of choices.

      +
    • +
    • list:getSelected()

      +

      Returns the selected index, choice, or nothing if the list is empty.

      +
    • +
    • list:getContentWidth()

      +

      Returns the minimal width to draw all choices without clipping.

      +
    • +
    • list:getContentHeight()

      +

      Returns the minimal width to draw all choices without scrolling.

      +
    • +
    • list:submit()

      +

      Call the on_submit callback, as if the Enter key was handled.

      +
    • +
    +
    +
    +

    FilteredList class

    +

    This widget combines List, EditField and Label into a combo-box like +construction that allows filtering the list by subwords of its items.

    +

    In addition to passing through all attributes supported by List, it +supports:

    + +++ + + + + + + +
    edit_pen:If specified, used instead of cursor_pen for the edit field.
    not_found_label:
     Specifies the text of the label shown when no items match the filter.
    +

    The list choices may include the following attributes:

    + +++ + + + +
    search_key:If specified, used instead of text to match against the filter.
    +

    The widget implements:

    +
      +
    • list:setChoices(choices[, selected])

      +

      Resets the filter, and passes through to the inner list.

      +
    • +
    • list:getChoices()

      +

      Returns the list of all choices.

      +
    • +
    • list:getFilter()

      +

      Returns the current filter string, and the filtered list of choices.

      +
    • +
    • list:setFilter(filter[,pos])

      +

      Sets the new filter string, filters the list, and selects the item at +index pos in the unfiltered list if possible.

      +
    • +
    • list:canSubmit()

      +

      Checks if there are currently any choices in the filtered list.

      +
    • +
    • list:getSelected(), list:getContentWidth(), list:getContentHeight(), list:submit()

      +

      Same as with an ordinary list.

      +
    • +
    +
    +
    +
    -

    Plugins

    +

    Plugins

    DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

    The following plugins have lua support.

    -

    burrows

    +

    burrows

    Implements extended burrow manipulations.

    Events:

      @@ -2169,13 +2978,13 @@ set is the same as used by the command line.

      The lua module file also re-exports functions from dfhack.burrows.

    -

    sort

    +

    sort

    Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

    -

    Scripts

    +

    Scripts

    Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans diff --git a/Lua API.rst b/Lua API.rst index ec48938db..d06e3d2e6 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1441,6 +1441,11 @@ Basic painting functions: Requests repaint of the screen by setting a flag. Unlike other functions in this section, this may be used at any time. +* ``dfhack.screen.getKeyDisplay(key)`` + + Returns the string that should be used to represent the given + logical keybinding on the screen in texts like "press Key to ...". + The "pen" argument used by functions above may be represented by a table with the following possible fields: @@ -1796,6 +1801,11 @@ environment by the mandatory init file dfhack.lua: safecall, qerror, mkmodule, reload +* Miscellaneous constants + + :NEWLINE, COMMA, PERIOD: evaluate to the relevant character strings. + :DEFAULT_NIL: is an unspecified unique token used by the class module below. + * ``printall(obj)`` If the argument is a lua table or DF object reference, prints all fields. @@ -1906,12 +1916,29 @@ utils as a guide to which values should be skipped as uninteresting. The ``force`` argument makes it always return a non-*nil* value. +* ``utils.parse_bitfield_int(value, type_ref)`` + + Given an int ``value``, and a bitfield type in the ``df`` tree, + it returns a lua table mapping the enabled bit keys to *true*, + unless value is 0, in which case it returns *nil*. + +* ``utils.list_bitfield_flags(bitfield[, list])`` + + Adds all enabled bitfield keys to ``list`` or a newly-allocated + empty sequence, and returns it. The ``bitfield`` argument may + be *nil*. + * ``utils.sort_vector(vector,field,cmpfun)`` Sorts a native vector or lua sequence using the comparator function. If ``field`` is not *nil*, applies the comparator to the field instead of the whole object. +* ``utils.linear_index(vector,key[,field])`` + + Searches for ``key`` in the vector, and returns *index, found_value*, + or *nil* if none found. + * ``utils.binsearch(vector,key,field,cmpfun,min,max)`` Does a binary search in a native vector or lua sequence for @@ -1947,6 +1974,23 @@ utils Exactly like ``erase_sorted_key``, but if field is specified, takes the key from ``item[field]``. +* ``utils.call_with_string(obj,methodname,...)`` + + Allocates a temporary string object, calls ``obj:method(tmp,...)``, and + returns the value written into the temporary after deleting it. + +* ``utils.getBuildingName(building)`` + + Returns the string description of the given building. + +* ``utils.getBuildingCenter(building)`` + + Returns an x/y/z table pointing at the building center. + +* ``utils.split_string(string, delimiter)`` + + Splits the string by the given delimiter, and returns a sequence of results. + * ``utils.prompt_yes_no(prompt, default)`` Presents a yes/no prompt to the user. If ``default`` is not *nil*, @@ -2006,19 +2050,32 @@ Implements a trivial single-inheritance class system. If the default value should be *nil*, use ``ATTRS { foo = DEFAULT_NIL }``. + Declaring an attribute is mostly the same as defining your ``init`` method like this:: + + function Class.init(args) + self.attr1 = args.attr1 or default1 + self.attr2 = args.attr2 or default2 + ... + end + + The main difference is that attributes are processed as a separate + initialization step, before any ``init`` methods are called. They + also make the directy relation between instance fields and constructor + arguments more explicit. + * ``new_obj = Class{ foo = arg, bar = arg, ... }`` Calling the class as a function creates and initializes a new instance. Initialization happens in this order: 1. An empty instance table is created, and its metatable set. - 2. The ``preinit`` method is called via ``invoke_before`` (see below) - with the table used as argument to the class. This method is intended + 2. The ``preinit`` methods are called via ``invoke_before`` (see below) + with the table used as argument to the class. These methods are intended for validating and tweaking that argument table. 3. Declared ATTRS are initialized from the argument table or their default values. - 4. The ``init`` method is called via ``invoke_after`` with the argument table. + 4. The ``init`` methods are called via ``invoke_after`` with the argument table. This is the main constructor method. - 5. The ``postinit`` method is called via ``invoke_after`` with the argument table. + 5. The ``postinit`` methods are called via ``invoke_after`` with the argument table. Place code that should be called after the object is fully constructed here. Predefined instance methods: @@ -2033,6 +2090,14 @@ Predefined instance methods: properly passing in self, and optionally a number of initial arguments too. The arguments given to the closure are appended to these. +* ``instance:cb_getfield(field_name)`` + + Returns a closure that returns the specified field of the object when called. + +* ``instance:cb_setfield(field_name)`` + + Returns a closure that sets the specified field to its argument when called. + * ``instance:invoke_before(method_name, args...)`` Navigates the inheritance chain of the instance starting from the most specific @@ -2057,6 +2122,715 @@ Predefined instance methods: To avoid confusion, these methods cannot be redefined. +================== +In-game UI Library +================== + +A number of lua modules with names starting with ``gui`` are dedicated +to wrapping the natives of the ``dfhack.screen`` module in a way that +is easy to use. This allows relatively easily and naturally creating +dialogs that integrate in the main game UI window. + +These modules make extensive use of the ``class`` module, and define +things ranging from the basic ``Painter``, ``View`` and ``Screen`` +classes, to fully functional predefined dialogs. + +gui +=== + +This module defines the most important classes and functions for +implementing interfaces. This documents those of them that are +considered stable. + + +Misc +---- + +* ``USE_GRAPHICS`` + + Contains the value of ``dfhack.screen.inGraphicsMode()``, which cannot be + changed without restarting the game and thus is constant during the session. + +* ``CLEAR_PEN`` + + The black pen used to clear the screen. + +* ``simulateInput(screen, keys...)`` + + This function wraps an undocumented native function that passes a set of + keycodes to a screen, and is the official way to do that. + + Every argument after the initial screen may be *nil*, a numeric keycode, + a string keycode, a sequence of numeric or string keycodes, or a mapping + of keycodes to *true* or *false*. For instance, it is possible to use the + table passed as argument to ``onInput``. + +* ``mkdims_xy(x1,y1,x2,y2)`` + + Returns a table containing the arguments as fields, and also ``width`` and + ``height`` that contains the rectangle dimensions. + +* ``mkdims_wh(x1,y1,width,height)`` + + Returns the same kind of table as ``mkdims_xy``, only this time it computes + ``x2`` and ``y2``. + +* ``is_in_rect(rect,x,y)`` + + Checks if the given point is within a rectangle, represented by a table produced + by one of the ``mkdims`` functions. + +* ``blink_visible(delay)`` + + Returns *true* or *false*, with the value switching to the opposite every ``delay`` + msec. This is intended for rendering blinking interface objects. + +* ``getKeyDisplay(keycode)`` + + Wraps ``dfhack.screen.getKeyDisplay`` in order to allow using strings for the keycode argument. + + +ViewRect class +-------------- + +This class represents an on-screen rectangle with an associated independent +clip area rectangle. It is the base of the ``Painter`` class, and is used by +``Views`` to track their client area. + +* ``ViewRect{ rect = ..., clip_rect = ..., view_rect = ..., clip_view = ... }`` + + The constructor has the following arguments: + + :rect: The ``mkdims`` rectangle in screen coordinates of the logical viewport. + Defaults to the whole screen. + :clip_rect: The clip rectangle in screen coordinates. Defaults to ``rect``. + :view_rect: A ViewRect object to copy from; overrides both ``rect`` and ``clip_rect``. + :clip_view: A ViewRect object to intersect the specified clip area with. + +* ``rect:isDefunct()`` + + Returns *true* if the clip area is empty, i.e. no painting is possible. + +* ``rect:inClipGlobalXY(x,y)`` + + Checks if these global coordinates are within the clip rectangle. + +* ``rect:inClipLocalXY(x,y)`` + + Checks if these coordinates (specified relative to ``x1,y1``) are within the clip rectangle. + +* ``rect:localXY(x,y)`` + + Converts a pair of global coordinates to local; returns *x_local,y_local*. + +* ``rect:globalXY(x,y)`` + + Converts a pair of local coordinates to global; returns *x_global,y_global*. + +* ``rect:viewport(x,y,w,h)`` or ``rect:viewport(subrect)`` + + Returns a ViewRect representing a sub-rectangle of the current one. + The arguments are specified in local coordinates; the ``subrect`` + argument must be a ``mkdims`` table. The returned object consists of + the exact specified rectangle, and a clip area produced by intersecting + it with the clip area of the original object. + + +Painter class +------------- + +The painting natives in ``dfhack.screen`` apply to the whole screen, are +completely stateless and don't implement clipping. + +The Painter class inherits from ViewRect to provide clipping and local +coordinates, and tracks current cursor position and current pen. + +* ``Painter{ ..., pen = ..., key_pen = ... }`` + + In addition to ViewRect arguments, Painter accepts a suggestion of + the initial value for the main pen, and the keybinding pen. They + default to COLOR_GREY and COLOR_LIGHTGREEN otherwise. + + There are also some convenience functions that wrap this constructor: + + - ``Painter.new(rect,pen)`` + - ``Painter.new_view(view_rect,pen)`` + - ``Painter.new_xy(x1,y1,x2,y2,pen)`` + - ``Painter.new_wh(x1,y1,width,height,pen)`` + +* ``painter:isValidPos()`` + + Checks if the current cursor position is within the clip area. + +* ``painter:viewport(x,y,w,h)`` + + Like the superclass method, but returns a Painter object. + +* ``painter:cursor()`` + + Returns the current cursor *x,y* in local coordinates. + +* ``painter:seek(x,y)`` + + Sets the current cursor position, and returns *self*. + Either of the arguments may be *nil* to keep the current value. + +* ``painter:advance(dx,dy)`` + + Adds the given offsets to the cursor position, and returns *self*. + Either of the arguments may be *nil* to keep the current value. + +* ``painter:newline([dx])`` + + Advances the cursor to the start of the next line plus the given x offset, and returns *self*. + +* ``painter:pen(...)`` + + Sets the current pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*. + +* ``painter:key_pen(...)`` + + Sets the current keybinding pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*. + +* ``painter:clear()`` + + Fills the whole clip rectangle with ``CLEAR_PEN``, and returns *self*. + +* ``painter:fill(x1,y1,x2,y2[,...])`` or ``painter:fill(rect[,...])`` + + Fills the specified local coordinate rectangle with ``dfhack.pen.parse(cur_pen,...)``, + and returns *self*. + +* ``painter:char([char[, ...]])`` + + Paints one character using ``char`` and ``dfhack.pen.parse(cur_pen,...)``; returns *self*. + The ``char`` argument, if not nil, is used to override the ``ch`` property of the pen. + +* ``painter:tile([char, tile[, ...]])`` + + Like above, but also allows overriding the ``tile`` property on ad-hoc basis. + +* ``painter:string(text[, ...])`` + + Paints the string with ``dfhack.pen.parse(cur_pen,...)``; returns *self*. + +* ``painter:key(keycode[, ...])`` + + Paints the description of the keycode using ``dfhack.pen.parse(cur_key_pen,...)``; returns *self*. + +As noted above, all painting methods return *self*, in order to allow chaining them like this:: + + painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')... + + +View class +---------- + +This class is the common abstract base of both the stand-alone screens +and common widgets to be used inside them. It defines the basic layout, +rendering and event handling framework. + +The class defines the following attributes: + +:visible: Specifies that the view should be painted. +:active: Specifies that the view should receive events, if also visible. +:view_id: Specifies an identifier to easily identify the view among subviews. + This is reserved for implementation of top-level views, and should + not be used by widgets for their internal subviews. + +It also always has the following fields: + +:subviews: Contains a table of all subviews. The sequence part of the + table is used for iteration. In addition, subviews are also + indexed under their *view_id*, if any; see ``addviews()`` below. + +These fields are computed by the layout process: + +:frame_parent_rect: The ViewRect represeting the client area of the parent view. +:frame_rect: The ``mkdims`` rect of the outer frame in parent-local coordinates. +:frame_body: The ViewRect representing the body part of the View's own frame. + +The class has the following methods: + +* ``view:addviews(list)`` + + Adds the views in the list to the ``subviews`` sequence. If any of the views + in the list have ``view_id`` attributes that don't conflict with existing keys + in ``subviews``, also stores them under the string keys. Finally, copies any + non-conflicting string keys from the ``subviews`` tables of the listed views. + + Thus, doing something like this:: + + self:addviews{ + Panel{ + view_id = 'panel', + subviews = { + Label{ view_id = 'label' } + } + } + } + + Would make the label accessible as both ``self.subviews.label`` and + ``self.subviews.panel.subviews.label``. + +* ``view:getWindowSize()`` + + Returns the dimensions of the ``frame_body`` rectangle. + +* ``view:getMousePos()`` + + Returns the mouse *x,y* in coordinates local to the ``frame_body`` + rectangle if it is within its clip area, or nothing otherwise. + +* ``view:updateLayout([parent_rect])`` + + Recomputes layout of the view and its subviews. If no argument is + given, re-uses the previous parent rect. The process goes as follows: + + 1. Calls ``preUpdateLayout(parent_rect)`` via ``invoke_before``. + 2. Uses ``computeFrame(parent_rect)`` to compute the desired frame. + 3. Calls ``postComputeFrame(frame_body)`` via ``invoke_after``. + 4. Calls ``updateSubviewLayout(frame_body)`` to update children. + 5. Calls ``postUpdateLayout(frame_body)`` via ``invoke_after``. + +* ``view:computeFrame(parent_rect)`` *(for overriding)* + + Called by ``updateLayout`` in order to compute the frame rectangle(s). + Should return the ``mkdims`` rectangle for the outer frame, and optionally + also for the body frame. If only one rectangle is returned, it is used + for both frames, and the margin becomes zero. + +* ``view:updateSubviewLayout(frame_body)`` + + Calls ``updateLayout`` on all children. + +* ``view:render(painter)`` + + Given the parent's painter, renders the view via the following process: + + 1. Calls ``onRenderFrame(painter, frame_rect)`` to paint the outer frame. + 2. Creates a new painter using the ``frame_body`` rect. + 3. Calls ``onRenderBody(new_painter)`` to paint the client area. + 4. Calls ``renderSubviews(new_painter)`` to paint visible children. + +* ``view:renderSubviews(painter)`` + + Calls ``render`` on all ``visible`` subviews in the order they + appear in the ``subviews`` sequence. + +* ``view:onRenderFrame(painter, rect)`` *(for overriding)* + + Called by ``render`` to paint the outer frame; by default does nothing. + +* ``view:onRenderBody(painter)`` *(for overriding)* + + Called by ``render`` to paint the client area; by default does nothing. + +* ``view:onInput(keys)`` *(for overriding)* + + Override this to handle events. By default directly calls ``inputToSubviews``. + Return a true value from this method to signal that the event has been handled + and should not be passed on to more views. + +* ``view:inputToSubviews(keys)`` + + Calls ``onInput`` on all visible active subviews, iterating the ``subviews`` + sequence in *reverse order*, so that topmost subviews get events first. + Returns *true* if any of the subviews handled the event. + + +Screen class +------------ + +This is a View subclass intended for use as a stand-alone dialog or screen. +It adds the following methods: + +* ``screen:isShown()`` + + Returns *true* if the screen is currently in the game engine's display stack. + +* ``screen:isDismissed()`` + + Returns *true* if the screen is dismissed. + +* ``screen:isActive()`` + + Returns *true* if the screen is shown and not dismissed. + +* ``screen:invalidate()`` + + Requests a repaint. Note that currently using it is not necessary, because + repaints are constantly requested automatically, due to issues with native + screens happening otherwise. + +* ``screen:renderParent()`` + + Asks the parent native screen to render itself, or clears the screen if impossible. + +* ``screen:sendInputToParent(...)`` + + Uses ``simulateInput`` to send keypresses to the native parent screen. + +* ``screen:show([parent])`` + + Adds the screen to the display stack with the given screen as the parent; + if parent is not specified, places this one one topmost. Before calling + ``dfhack.screen.show``, calls ``self:onAboutToShow(parent)``. + +* ``screen:onAboutToShow(parent)`` *(for overriding)* + + Called when ``dfhack.screen.show`` is about to be called. + +* ``screen:onShow()`` + + Called by ``dfhack.screen.show`` once the screen is successfully shown. + +* ``screen:dismiss()`` + + Dismisses the screen. A dismissed screen does not receive any more + events or paint requests, but may remain in the display stack for + a short time until the game removes it. + +* ``screen:onDismiss()`` *(for overriding)* + + Called by ``dfhack.screen.dismiss()``. + +* ``screen:onDestroy()`` *(for overriding)* + + Called by the native code when the screen is fully destroyed and removed + from the display stack. Place code that absolutely must be called whenever + the screen is removed by any means here. + +* ``screen:onResize``, ``screen:onRender`` + + Defined as callbacks for native code. + + +FramedScreen class +------------------ + +A Screen subclass that paints a visible frame around its body. +Most dialogs should inherit from this class. + +A framed screen has the following attributes: + +:frame_style: A table that defines a set of pens to draw various parts of the frame. +:frame_title: A string to display in the middle of the top of the frame. +:frame_width: Desired width of the client area. If *nil*, the screen will occupy the whole width. +:frame_height: Likewise, for height. +:frame_inset: The gap between the frame and the client area. Defaults to 0. +:frame_background: The pen to fill in the frame with. Defaults to CLEAR_PEN. + +There are the following predefined frame style tables: + +* ``GREY_FRAME`` + + A plain grey-colored frame. + +* ``BOUNDARY_FRAME`` + + The same frame as used by the usual full-screen DF views, like dwarfmode. + +* ``GREY_LINE_FRAME`` + + A frame consisting of grey lines, similar to the one used by titan announcements. + + +gui.widgets +=========== + +This module implements some basic widgets based on the View infrastructure. + +Widget class +------------ + +Base of all the widgets. Inherits from View and has the following attributes: + +* ``frame = {...}`` + + Specifies the constraints on the outer frame of the widget. + If omitted, the widget will occupy the whole parent rectangle. + + The frame is specified as a table with the following possible fields: + + :l: gap between the left edges of the frame and the parent. + :t: gap between the top edges of the frame and the parent. + :r: gap between the right edges of the frame and the parent. + :b: gap between the bottom edges of the frame and the parent. + :w: maximum width of the frame. + :h: maximum heigth of the frame. + :xalign: X alignment of the frame. + :yalign: Y alignment of the frame. + + First the ``l,t,r,b`` fields restrict the available area for + placing the frame. If ``w`` and ``h`` are not specified or + larger then the computed area, it becomes the frame. Otherwise + the smaller frame is placed within the are based on the + ``xalign/yalign`` fields. If the align hints are omitted, they + are assumed to be 0, 1, or 0.5 based on which of the ``l/r/t/b`` + fields are set. + +* ``frame_inset = {...}`` + + Specifies the gap between the outer frame, and the client area. + The attribute may be a simple integer value to specify a uniform + inset, or a table with the following fields: + + :l: left margin. + :t: top margin. + :r: right margin. + :b: bottom margin. + :x: left/right margin, if ``l`` and/or ``r`` are omitted. + :y: top/bottom margin, if ``t`` and/or ``b`` are omitted. + +* ``frame_background = pen`` + + The pen to fill the outer frame with. Defaults to no fill. + +Panel class +----------- + +Inherits from Widget, and intended for grouping a number of subviews. + +Has attributes: + +* ``subviews = {}`` + + Used to initialize the subview list in the constructor. + +* ``on_render = function(painter)`` + + Called from ``onRenderBody``. + +Pages class +----------- + +Subclass of Panel; keeps exactly one child visible. + +* ``Pages{ ..., selected = ... }`` + + Specifies which child to select initially; defaults to the first one. + +* ``pages:getSelected()`` + + Returns the selected *index, child*. + +* ``pages:setSelected(index)`` + + Selects the specified child, hiding the previous selected one. + It is permitted to use the subview object, or its ``view_id`` as index. + +EditField class +--------------- + +Subclass of Widget; implements a simple edit field. + +Attributes: + +:text: The current contents of the field. +:text_pen: The pen to draw the text with. +:on_char: Input validation callback; used as ``on_char(new_char,text)``. + If it returns false, the character is ignored. +:on_change: Change notification callback; used as ``on_change(new_text,old_text)``. +:on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. + +Label class +----------- + +This Widget subclass implements flowing semi-static text. + +It has the following attributes: + +:text_pen: Specifies the pen for active text. +:text_dpen: Specifies the pen for disabled text. +:disabled: Boolean or a callback; if true, the label is disabled. +:enabled: Boolean or a callback; if false, the label is disabled. +:auto_height: Sets self.frame.h from the text height. +:auto_width: Sets self.frame.w from the text width. + +The text itself is represented as a complex structure, and passed +to the object via the ``text`` argument of the constructor, or via +the ``setText`` method, as one of: + +* A simple string, possibly containing newlines. +* A sequence of tokens. + +Every token in the sequence in turn may be either a string, possibly +containing newlines, or a table with the following possible fields: + +* ``token.text = ...`` + + Specifies the main text content of a token, and may be a string, or + a callback returning a string. + +* ``token.gap = ...`` + + Specifies the number of character positions to advance on the line + before rendering the token. + +* ``token.tile = pen`` + + Specifies a pen to paint as one tile before the main part of the token. + +* ``token.key = '...'`` + + Specifies the keycode associated with the token. The string description + of the key binding is added to the text content of the token. + +* ``token.key_sep = '...'`` + + Specifies the separator to place between the keybinding label produced + by ``token.key``, and the main text of the token. If the separator is + '()', the token is formatted as ``text..' ('..binding..')'``. Otherwise + it is simply ``binding..sep..text``. + +* ``token.enabled``, ``token.disabled`` + + Same as the attributes of the label itself, but applies only to the token. + +* ``token.pen``, ``token.dpen`` + + Specify the pen and disabled pen to be used for the token's text. + The field may be either the pen itself, or a callback that returns it. + +* ``token.on_activate`` + + If this field is not nil, and ``token.key`` is set, the token will actually + respond to that key binding unless disabled, and call this callback. Eventually + this may be extended with mouse click support. + +* ``token.id`` + + Specifies a unique identifier for the token. + +* ``token.line``, ``token.x1``, ``token.x2`` + + Reserved for internal use. + +The Label widget implements the following methods: + +* ``label:setText(new_text)`` + + Replaces the text currently contained in the widget. + +* ``label:itemById(id)`` + + Finds a token by its ``id`` field. + +* ``label:getTextHeight()`` + + Computes the height of the text. + +* ``label:getTextWidth()`` + + Computes the width of the text. + +List class +---------- + +The List widget implements a simple list with paging. + +It has the following attributes: + +:text_pen: Specifies the pen for deselected list entries. +:cursor_pen: Specifies the pen for the selected entry. +:inactive_pen: If specified, used for the cursor when the widget is not active. +:icon_pen: Default pen for icons. +:on_select: Selection change callback; called as ``on_select(index,choice)``. +:on_submit: Enter key callback; if specified, the list reacts to the key + and calls it as ``on_submit(index,choice)``. +:row_height: Height of every row in text lines. +:icon_width: If not *nil*, the specified number of character columns + are reserved to the left of the list item for the icons. +:scroll_keys: Specifies which keys the list should react to as a table. + +Every list item may be specified either as a string, or as a lua table +with the following fields: + +:text: Specifies the label text in the same format as the Label text. +:caption, [1]: Deprecated legacy aliases for **text**. +:text_*: Reserved for internal use. +:key: Specifies a keybinding that acts as a shortcut for the specified item. +:icon: Specifies an icon string, or a pen to paint a single character. May be a callback. +:icon_pen: When the icon is a string, used to paint it. + +The list supports the following methods: + +* ``List{ ..., choices = ..., selected = ... }`` + + Same as calling ``setChoices`` after construction. + +* ``list:setChoices(choices[, selected])`` + + Replaces the list of choices, possibly also setting the currently selected index. + +* ``list:setSelected(selected)`` + + Sets the currently selected index. Returns the index after validation. + +* ``list:getChoices()`` + + Returns the list of choices. + +* ``list:getSelected()`` + + Returns the selected *index, choice*, or nothing if the list is empty. + +* ``list:getContentWidth()`` + + Returns the minimal width to draw all choices without clipping. + +* ``list:getContentHeight()`` + + Returns the minimal width to draw all choices without scrolling. + +* ``list:submit()`` + + Call the ``on_submit`` callback, as if the Enter key was handled. + +FilteredList class +------------------ + +This widget combines List, EditField and Label into a combo-box like +construction that allows filtering the list by subwords of its items. + +In addition to passing through all attributes supported by List, it +supports: + +:edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. +:not_found_label: Specifies the text of the label shown when no items match the filter. + +The list choices may include the following attributes: + +:search_key: If specified, used instead of **text** to match against the filter. + +The widget implements: + +* ``list:setChoices(choices[, selected])`` + + Resets the filter, and passes through to the inner list. + +* ``list:getChoices()`` + + Returns the list of *all* choices. + +* ``list:getFilter()`` + + Returns the current filter string, and the *filtered* list of choices. + +* ``list:setFilter(filter[,pos])`` + + Sets the new filter string, filters the list, and selects the item at + index ``pos`` in the *unfiltered* list if possible. + +* ``list:canSubmit()`` + + Checks if there are currently any choices in the filtered list. + +* ``list:getSelected()``, ``list:getContentWidth()``, ``list:getContentHeight()``, ``list:submit()`` + + Same as with an ordinary list. + ======= Plugins diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e731af068..ad408a2ea 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -504,7 +504,7 @@ function List:onRenderBody(dc) local icon = getval(obj.icon) if icon then dc:seek(0, y) - if type(icon) == 'table' then + if type(icon) ~= 'string' then dc:char(nil,icon) else if current then @@ -573,7 +573,7 @@ FilteredList = defclass(FilteredList, Widget) function FilteredList:init(info) self.edit = EditField{ - text_pen = info.cursor_pen, + text_pen = info.edit_pen or info.cursor_pen, frame = { l = info.icon_width, t = 0 }, on_change = self:callback('onFilterChange'), on_char = self:callback('onFilterChar'), @@ -583,6 +583,7 @@ function FilteredList:init(info) text_pen = info.text_pen, cursor_pen = info.cursor_pen, inactive_pen = info.inactive_pen, + icon_pen = info.icon_pen, row_height = info.row_height, scroll_keys = info.scroll_keys, icon_width = info.icon_width, diff --git a/library/lua/utils.lua b/library/lua/utils.lua index e7267038c..6d6b4de5c 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -457,6 +457,7 @@ function getBuildingCenter(building) return xyz2pos(building.centerx, building.centery, building.z) end +-- Split the string by the given delimiter function split_string(self, delimiter) local result = { } local from = 1 From edf80ff748ad8193015a98936e5ac86263bf9ac7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 4 Nov 2012 18:03:02 +0400 Subject: [PATCH 16/24] Show the sex of the unit in the bottom line of Dwarf Manipulator. --- plugins/manipulator.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 1bc0195b6..79999d468 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1067,18 +1067,22 @@ void viewscreen_unitlaborsst::render() if (cur != NULL) { df::unit *unit = cur->unit; - int x = 1; - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->transname); + int x = 1, y = 3 + num_rows + 2; + Screen::Pen white_pen(' ', 15, 0); + + Screen::paintString(white_pen, x, y, (cur->unit && cur->unit->sex) ? "\x0b" : "\x0c"); + x += 2; + Screen::paintString(white_pen, x, y, cur->transname); x += cur->transname.length(); if (cur->transname.length()) { - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ", "); + Screen::paintString(white_pen, x, y, ", "); x += 2; } - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->profession); + Screen::paintString(white_pen, x, y, cur->profession); x += cur->profession.length(); - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ": "); + Screen::paintString(white_pen, x, y, ": "); x += 2; string str; From 20e98d49263c2e1d719a57fc3c138dfafb32fd8f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 4 Nov 2012 20:51:13 +0400 Subject: [PATCH 17/24] Add a script for viewing and poking at local populations. --- scripts/region-pops.lua | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 scripts/region-pops.lua diff --git a/scripts/region-pops.lua b/scripts/region-pops.lua new file mode 100644 index 000000000..779167134 --- /dev/null +++ b/scripts/region-pops.lua @@ -0,0 +1,141 @@ +-- Shows populations of animals in the region, and allows tweaking them. + +local utils = require 'utils' + +local function sort_keys(tab) + local kt = {} + for k,v in pairs(tab) do table.insert(kt,k) end + table.sort(kt) + return ipairs(kt) +end + +local is_plant_map = { + Animal = false, Vermin = false, VerminInnumerable = false, + ColonyInsect = false, Tree = true, Grass = true, Bush = true +} + +function enum_populations() + local stat_table = { + plants = {}, + creatures = {}, + any = {} + } + + for i,v in ipairs(df.global.world.populations) do + local typeid = df.world_population_type[v.type] + local is_plant = is_plant_map[typeid] + local id, obj, otable, idtoken + + if is_plant then + id = v.plant + obj = df.plant_raw.find(id) + otable = stat_table.plants + idtoken = obj.id + else + id = v.race + obj = df.creature_raw.find(id) + otable = stat_table.creatures + idtoken = obj.creature_id + end + + local entry = otable[idtoken] + if not entry then + entry = { + obj = obj, token = idtoken, id = id, records = {}, + count = 0, known_count = 0, + known = false, infinite = false + } + otable[idtoken] = entry + stat_table.any[idtoken] = entry + end + + table.insert(entry.records, v) + entry.known = entry.known or v.known + + if v.quantity < 10000001 then + entry.count = entry.count + v.quantity + if v.known then + entry.known_count = entry.known_count + v.quantity + end + else + entry.infinite = true + end + end + + return stat_table +end + +function list_poptable(entries, all, pattern) + for _,k in sort_keys(entries) do + local entry = entries[k] + if (all or entry.known) and (not pattern or string.match(k,pattern)) then + local count = entry.known_count + if all then + count = entry.count + end + if entry.infinite then + count = 'innumerable' + end + print(string.format('%-40s %s', entry.token, count)) + end + end +end + +function list_populations(stat_table, all, pattern) + print('Plants:') + list_poptable(stat_table.plants, true, pattern) + print('\nCreatures and vermin:') + list_poptable(stat_table.creatures, all, pattern) +end + + +function boost_population(entry, factor, boost_count) + for _,v in ipairs(entry.records) do + if v.quantity < 10000001 then + boost_count = boost_count + 1 + v.quantity = math.floor(v.quantity * factor) + end + end + return boost_count +end + +local args = {...} +local pops = enum_populations() + +if args[1] == 'list' or args[1] == 'list-all' then + list_populations(pops, args[1] == 'list-all', args[2]) +elseif args[1] == 'boost' or args[1] == 'boost-all' then + local factor = tonumber(args[3]) + if not factor or factor < 0 then + qerror('Invalid boost factor.') + end + + local count = 0 + + if args[1] == 'boost' then + local entry = pops.any[args[2]] or qerror('Unknown population token.') + count = boost_population(entry, factor, count) + else + for k,entry in pairs(pops.any) do + if string.match(k, args[2]) then + count = boost_population(entry, factor, count) + end + end + end + + print('Updated '..count..' populations.') +else + print([[ +Usage: + region-pops list [pattern] + Lists encountered populations of the region, possibly restricted by pattern. + region-pops list-all [pattern] + Lists all populations of the region. + region-pops boost + Multiply all populations of TOKEN by factor. + If the factor is greater than one, increases the + population, otherwise decreases it. + region-pops boost-all + Same as above, but match using a pattern acceptable to list. +]]) +end From ff982dcf738ba766270663d8fc751c55c1fe8699 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 7 Nov 2012 13:31:36 +0400 Subject: [PATCH 18/24] Fix representation and parsing of built-in materials. The trick is to support both FOO and FOO:NONE for all of them, including INORGANIC[:NONE]. Otherwise the workflow gui scripts have problems. --- NEWS | 2 ++ dfhack.init-example | 2 +- library/modules/Materials.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index d5080b05a..f2237cab9 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + New scripts: + - region-pops: displays animal populations of the region and allows tweaking them. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index d7f3f5399..20048e39e 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -18,7 +18,7 @@ keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave # gui/rename script keybinding add Ctrl-Shift-N gui/rename -keybinding add Alt-Shift-P "gui/rename unit-profession" +keybinding add Ctrl-Shift-T "gui/rename unit-profession" ############################## # Generic adv mode bindings # diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 454fdf66d..7c06aeb4c 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -190,6 +190,8 @@ bool MaterialInfo::find(const std::vector &items) } else if (items.size() == 2) { + if (items[1] == "NONE" && findBuiltin(items[0])) + return true; if (findPlant(items[0], items[1])) return true; if (findCreature(items[0], items[1])) @@ -210,7 +212,7 @@ bool MaterialInfo::findBuiltin(const std::string &token) } df::world_raws &raws = world->raws; - for (int i = 1; i < NUM_BUILTIN; i++) + for (int i = 0; i < NUM_BUILTIN; i++) { auto obj = raws.mat_table.builtin[i]; if (obj && obj->id == token) @@ -312,7 +314,7 @@ std::string MaterialInfo::getToken() else if (index == 1) return "COAL:CHARCOAL"; } - return material->id + ":NONE"; + return material->id; case Inorganic: return "INORGANIC:" + inorganic->id; case Creature: From f6b6d730a08802deb9733e0119b5b8b24097bd0f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 7 Nov 2012 22:49:40 +0400 Subject: [PATCH 19/24] Fix fix-armory constantly trying to store ammo already stored in chest. --- plugins/fix-armory.cpp | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index ade9e4252..b937d40e8 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -160,7 +160,7 @@ static bool is_assigned_item(df::item *item) } // Check if this ammo item is assigned to this squad with one of the specified uses -static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool combat) +static bool is_squad_ammo(df::item *item, df::squad *squad, bool combat, bool train) { for (size_t i = 0; i < squad->ammunition.size(); i++) { @@ -186,8 +186,6 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i if (squads) { - bool target = holder->getType() == building_type::ArcheryTarget; - for (size_t i = 0; i < squads->size(); i++) { auto use = (*squads)[i]; @@ -198,8 +196,7 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i // Squad Equipment -> combat bool combat = use->mode.bits.squad_eq; - // Archery target with Train -> training - bool train = target && use->mode.bits.train; + bool train = false; if (combat || train) { @@ -210,6 +207,41 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i } } } + // Ugh, archery targets don't actually have a squad use vector + else if (holder->getType() == building_type::ArcheryTarget) + { + auto &squads = df::global::world->squads.all; + + for (size_t si = 0; si < squads.size(); si++) + { + auto squad = squads[si]; + + // For containers assigned to a squad, only consider that squad + if (squad_id >= 0 && squad->id != squad_id) + continue; + + for (size_t j = 0; j < squad->rooms.size(); j++) + { + auto use = squad->rooms[j]; + + if (use->building_id != holder->id) + continue; + + // Squad Equipment -> combat + bool combat = use->mode.bits.squad_eq; + // Archery target with Train -> training + bool train = use->mode.bits.train; + + if (combat || train) + { + if (is_squad_ammo(item, squad, combat, train)) + return true; + } + + break; + } + } + } for (size_t i = 0; i < holder->parents.size(); i++) if (can_store_ammo_rec(item, holder->parents[i], squad_id)) From 0c70a448d0f88823dd6ac236e8d123fa79da966f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Wed, 7 Nov 2012 23:06:02 +0100 Subject: [PATCH 20/24] Update submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index fcacacce7..4ab899319 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fcacacce7cf09cf70f011fea87b5be416da73457 +Subproject commit 4ab899319014d950214714a48cd3049a4beb5eb5 diff --git a/plugins/stonesense b/plugins/stonesense index 75df76626..cb97cf308 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 75df766263b23182820a1e07b330e64f87d5c9b7 +Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd From a7bf526f41216f6e43c942c21907dbd46fe8f44f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 8 Nov 2012 21:27:56 +0400 Subject: [PATCH 21/24] Make workflow consider squad-assigned items busy. --- NEWS | 1 + plugins/workflow.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f2237cab9..32181f16a 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ DFHack future - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. - logic fix: collecting webs produces silk, and ungathered webs are not thread. + - items assigned to squads are considered busy, even if not in inventory. New Fix Armory plugin: Together with a couple of binary patches and the gui/assign-rack script, this plugin makes weapon racks, armor stands, chests and cabinets in diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index d46daed44..5347d4671 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1004,6 +1004,12 @@ static bool isRouteVehicle(df::item *item) return vehicle && vehicle->route_id >= 0; } +static bool isAssignedSquad(df::item *item) +{ + auto &vec = ui->equipment.items_assigned[item->getType()]; + return binsearch_index(vec, &df::item::id, item->id) >= 0; +} + static void map_job_items(color_ostream &out) { for (size_t i = 0; i < constraints.size(); i++) @@ -1117,8 +1123,10 @@ static void map_job_items(color_ostream &out) item->isAssignedToStockpile() || isRouteVehicle(item) || itemInRealJob(item) || - itemBusy(item)) + itemBusy(item) || + isAssignedSquad(item)) { + is_invalid = true; cv->item_inuse++; } else From eb936c4ce07bb16684de0157a2ab5bbdf74fc4ca Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 10 Nov 2012 17:06:54 +0400 Subject: [PATCH 22/24] Support milking and shearing in workflow. --- NEWS | 1 + library/modules/Job.cpp | 2 +- library/modules/Materials.cpp | 2 ++ library/xml | 2 +- plugins/lua/workflow.lua | 23 +++++++++++++++++------ plugins/workflow.cpp | 4 +++- scripts/gui/workflow.lua | 2 +- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 32181f16a..51321be95 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,7 @@ DFHack future - code for deducing job outputs rewritten in lua for flexibility. - logic fix: collecting webs produces silk, and ungathered webs are not thread. - items assigned to squads are considered busy, even if not in inventory. + - shearing and milking jobs are supported, but only with generic MILK or YARN outputs. New Fix Armory plugin: Together with a couple of binary patches and the gui/assign-rack script, this plugin makes weapon racks, armor stands, chests and cabinets in diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index def3b4192..757000885 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -75,7 +75,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) { df::general_ref *ref = pnew->references[i]; - if (virtual_cast(ref)) + if (virtual_cast(ref)) vector_erase_at(pnew->references, i); else pnew->references[i] = ref->clone(); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 7c06aeb4c..4da484ade 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -425,6 +425,8 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) TEST(glass, IS_GLASS); if (cat.bits.clay && linear_index(material->reaction_product.id, std::string("FIRED_MAT")) >= 0) return true; + if (cat.bits.milk && linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0) + return true; return false; } diff --git a/library/xml b/library/xml index 4ab899319..02e0e0d7b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4ab899319014d950214714a48cd3049a4beb5eb5 +Subproject commit 02e0e0d7b9a7ef708a621ef5511a24bf8657b4a2 diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 4c011b24c..c3dbe20d9 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -301,15 +301,26 @@ function listWeakenedConstraints(outputs) local mask = cons.mat_mask if (cons.mat_type or -1) >= 0 then cons.mat_mask = nil + local info = dfhack.matinfo.decode(cons) + if info then + for i,flag in ipairs(df.dfhack_material_category) do + if flag and flag ~= 'wood2' and info:matches{[flag]=true} then + mask = mask or {} + mask[flag] = true + end + end + end end register(cons) if mask then - table.insert(generic, { - item_type = cons.item_type, - item_subtype = cons.item_subtype, - is_craft = cons.is_craft, - mat_mask = mask - }) + for k,v in pairs(mask) do + table.insert(generic, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft, + mat_mask = { [k] = v } + }) + end end table.insert(anymat, { item_type = cons.item_type, diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 5347d4671..c89d87333 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -377,7 +377,9 @@ static bool isSupportedJob(df::job *job) Job::getHolder(job) && (!job->job_items.empty() || job->job_type == job_type::CollectClay || - job->job_type == job_type::CollectSand); + job->job_type == job_type::CollectSand || + job->job_type == job_type::MilkCreature || + job->job_type == job_type::ShearCreature); } static bool isOptionEnabled(unsigned flag) diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 8dc958062..366e3ec91 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -306,7 +306,7 @@ function JobConstraints:onNewConstraint() end dlg.showListPrompt( - 'Job Outputs', + 'New limit', 'Select one of the possible outputs:', COLOR_WHITE, choices, From 56ef33ea0e5c6236239b4af43f00cb182c181987 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 10 Nov 2012 17:33:05 +0400 Subject: [PATCH 23/24] Support building steam engines on top of brooks without any down stairs. --- library/include/TileTypes.h | 6 ++++++ library/xml | 2 +- plugins/steam-engine.cpp | 8 +++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index d21fb3c17..48c10146a 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -203,6 +203,12 @@ namespace DFHack return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); } + inline + bool FlowPassableDown(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, passable_flow_down, tileShape(tiletype)); + } + inline bool isWalkable(df::tiletype tiletype) { diff --git a/library/xml b/library/xml index 02e0e0d7b..4b2124957 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 02e0e0d7b9a7ef708a621ef5511a24bf8657b4a2 +Subproject commit 4b2124957e282683480eaf05922e63c353364ec1 diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index d884191e5..60f38ef83 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -320,7 +320,7 @@ struct workshop_hook : df::building_workshopst { for (int y = y1; y <= y2; y++) { auto ptile = Maps::getTileType(x,y,z); - if (!ptile || !LowPassable(*ptile)) + if (!ptile || !FlowPassableDown(*ptile)) continue; auto pltile = Maps::getTileType(x,y,z-1); @@ -891,7 +891,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed); * Scan raws for matching workshop buildings. */ -static bool find_engines() +static bool find_engines(color_ostream &out) { engines.clear(); @@ -943,6 +943,8 @@ static bool find_engines() if (!ws.gear_tiles.empty()) engines.push_back(ws); + else + out.printerr("%s has no gear tiles - ignoring.\n", wslist[i]->code.c_str()); } return !engines.empty(); @@ -973,7 +975,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { switch (event) { case SC_WORLD_LOADED: - if (find_engines()) + if (find_engines(out)) { out.print("Detected steam engine workshops - enabling plugin.\n"); enable_hooks(true); From f86371cfc3114dc08963cb3d0a521ca3660ac5ad Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 10 Nov 2012 18:06:41 +0400 Subject: [PATCH 24/24] Try blocking any use of stockpiles for squad stuff in fix-armory. --- plugins/fix-armory.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index b937d40e8..99e5fd500 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -132,6 +132,9 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) * grace period during which the items can be instantly picked up again. */ +// Completely block the use of stockpiles +#define NO_STOCKPILES + // Check if the item is assigned to any use controlled by the military tab static bool is_assigned_item(df::item *item) { @@ -143,19 +146,6 @@ static bool is_assigned_item(df::item *item) if (idx < 0) return false; - // Exclude weapons used by miners, wood cutters etc - switch (type) { - case item_type::WEAPON: - // the game code also checks this for ammo, funnily enough - // maybe it's not just for weapons?.. - if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0) - return false; - break; - - default: - break; - } - return true; } @@ -328,6 +318,16 @@ template struct armory_hook : Item { */ DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { +#ifdef NO_STOCKPILES + /* + * Completely block any items assigned to a squad from being stored + * in stockpiles. The reason is that I still observe haulers running + * around with bins to pick them up for some reason. There could be + * some unaccounted race conditions involved. + */ + if (is_assigned_item(this)) + return false; +#else // Block stockpiling of items in the armory. if (is_in_armory(this)) return false; @@ -354,6 +354,7 @@ template struct armory_hook : Item { return false; } } +#endif // Call the original vmethod return INTERPOSE_NEXT(isCollected)();