From abe027c940ec3efcea7955d45708d2c7e8916295 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 30 Nov 2012 22:44:05 +1300 Subject: [PATCH 01/22] Copy changes from ag fork --- plugins/search.cpp | 154 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 34 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index fdc788955..a14397fba 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) { @@ -206,12 +225,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]) { @@ -245,6 +264,9 @@ protected: update_secondary_values(); *sort_list2 = saved_list2; } + + saved_list1.clear(); + saved_list2.clear(); } store_reference_values(); search_string = ""; @@ -278,7 +300,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)); @@ -307,8 +329,9 @@ protected: // Display hotkey message void print_search_option(int x, int y = -1) const { + auto dim = Screen::getWindowSize(); if (y == -1) - y = gps->dimy - 2; + y = dim.y - 2; OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key)); OutputString((entry_mode) ? 10 : 15, x, y, ": Search"); @@ -346,7 +369,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 +385,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,11 +411,12 @@ public: virtual void render() const { if (!viewscreen->in_group_mode) - print_search_option(1); + print_search_option(2); else { - int x = 1; - OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(15, x, y, "Tab to enable Search"); } } @@ -402,13 +432,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; } @@ -440,8 +475,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 @@ -461,28 +496,33 @@ 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; } virtual bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L)) + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + (!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) { if (!is_entry_mode()) { @@ -502,8 +542,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 @@ -529,6 +569,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,20 +603,25 @@ 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; } }; 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 @@ -564,20 +632,25 @@ 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; } }; 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 +662,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 || !gview || + !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 +691,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_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: + break; + } + return CR_OK; -} \ No newline at end of file +} From 210c1650ecb19a9407779b37ff334f28dca859a7 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 1 Dec 2012 01:01:04 +1300 Subject: [PATCH 02/22] Add stockpile screen searching capability --- plugins/search.cpp | 122 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 13 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index a14397fba..cf1798dcf 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -7,10 +7,12 @@ //#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_layer_stockpilest.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" #include "df/interface_key.h" #include "df/interfacest.h" +#include "df/layer_object_listst.h" using std::set; using std::vector; @@ -79,6 +81,11 @@ public: return true; } + bool is_valid() + { + return valid; + } + // A new keystroke is received in a searchable screen virtual bool process_input(set *input) { @@ -164,9 +171,9 @@ protected: const S *viewscreen; vector saved_list1, reference_list; vector saved_list2; + vector *sort_list2; vector saved_indexes; - bool valid; bool redo_search; bool track_secondary_values; string search_string; @@ -240,12 +247,17 @@ protected: } } - saved_list2[saved_indexes[i]] = (*sort_list2)[adjusted_item_index]; + update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); } saved_indexes.clear(); } } + virtual void update_saved_secondary_list_item(size_t i, size_t j) + { + saved_list2[i] = (*sort_list2)[j]; + } + // Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering void store_reference_values() { @@ -254,7 +266,7 @@ protected: } // Shortcut to clear the search immediately - void clear_search() + virtual void clear_search() { if (saved_list1.size() > 0) { @@ -273,7 +285,7 @@ protected: } // The actual sort - void do_search() + virtual void do_search() { if (search_string.length() == 0) { @@ -318,7 +330,8 @@ protected: store_reference_values(); //Keep a copy, in case user sorts new list - *cursor_pos = 0; + if (cursor_pos) + *cursor_pos = 0; } virtual bool should_check_input(set *input) @@ -346,10 +359,9 @@ protected: private: vector *sort_list1; - vector *sort_list2; int *cursor_pos; char select_key; - + bool valid; bool entry_mode; df::interface_key select_token; @@ -359,7 +371,8 @@ private: }; template search_parent *search_parent ::lock = NULL; -// Parent struct for the hooks +// Parent struct for the hooks, use optional param D to generate multiple classes with same T & V +// but different static modules template struct search_hook : T { @@ -437,7 +450,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->item_cursor, &screen->items); @@ -501,7 +514,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); @@ -608,7 +621,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); @@ -637,7 +650,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); @@ -657,6 +670,84 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); // +// +// START: Stockpile screen search +// + +class stockpile_search : public search_parent +{ +public: + void update_saved_secondary_list_item(size_t i, size_t j) + { + *saved_list2[i] = *(*sort_list2)[j]; + } + + string get_element_description(string *element) const + { + return *element; + } + + void render() const + { + print_search_option(2, 25); + } + + static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer, int idx) + { + return virtual_cast(vector_get(layer->layer_objects,idx)); + } + + bool init(df::viewscreen_layer_stockpilest *screen) + { + if (screen != viewscreen && !reset_on_change()) + return false; + + auto list3 = getLayerList(screen, 2); + if (!list3->active) + { + if (is_valid()) + { + clear_search(); + reset_all(); + } + + return false; + } + + if (!is_valid()) + { + viewscreen = screen; + search_parent::init(&list3->cursor, &screen->item_names, &screen->item_status); + track_secondary_values = true; + } + + return true; + } + + void do_search() + { + search_parent::do_search(); + auto list3 = getLayerList(viewscreen, 2); + list3->num_entries = viewscreen->item_names.size(); + } + + void clear_search() + { + search_parent::clear_search(); + auto list3 = getLayerList(viewscreen, 2); + list3->num_entries = viewscreen->item_names.size(); + } +}; + +typedef search_hook stockpile_search_hook; +template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, render); + +// +// END: Stockpile screen search +// + + DFHACK_PLUGIN("search"); @@ -670,7 +761,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Sat, 1 Dec 2012 16:50:03 +0400 Subject: [PATCH 03/22] Implement the history graph in the workflow status screen. --- Lua API.html | 14 +- Lua API.rst | 2 + Readme.html | 27 +++- Readme.rst | 33 ++++- dfhack.init-example | 1 + images/workflow-new1.png | Bin 6674 -> 6775 bytes images/workflow-new2.png | Bin 6862 -> 7793 bytes images/workflow-status.png | Bin 0 -> 5118 bytes images/workflow.png | Bin 4931 -> 5779 bytes library/lua/gui/widgets.lua | 19 ++- scripts/gui/workflow.lua | 248 ++++++++++++++++++++++++------------ 11 files changed, 248 insertions(+), 96 deletions(-) create mode 100644 images/workflow-status.png diff --git a/Lua API.html b/Lua API.html index f42905d01..f14239a10 100644 --- a/Lua API.html +++ b/Lua API.html @@ -2787,6 +2787,14 @@ before rendering the token.

  • token.tile = pen

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

  • +
  • token.width = ...

    +

    If specified either as a value or a callback, the text field is padded +or truncated to the specified number.

    +
  • +
  • token.pad_char = '?'

    +

    If specified together with width, the padding area is filled with +this character instead of just being skipped over.

    +
  • 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.

    @@ -2848,7 +2856,9 @@ this may be extended with mouse click support.

    icon_pen:Default pen for icons. -on_select:Selection change callback; called as on_select(index,choice). +on_select:Selection change callback; called as on_select(index,choice). +This is also called with nil arguments if setChoices is called +with an empty list. on_submit:Enter key callback; if specified, the list reacts to the key and calls it as on_submit(index,choice). @@ -2928,6 +2938,8 @@ supports:

    edit_pen:If specified, used instead of cursor_pen for the edit field. +edit_below:If true, the edit field is placed below the list instead of above. + not_found_label:  Specifies the text of the label shown when no items match the filter. diff --git a/Lua API.rst b/Lua API.rst index 714a41bfb..cedc36441 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2785,6 +2785,8 @@ It has the following attributes: :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)``. + This is also called with *nil* arguments if ``setChoices`` is called + with an empty list. :on_submit: Enter key callback; if specified, the list reacts to the key and calls it as ``on_submit(index,choice)``. :on_submit2: Shift-Enter key callback; if specified, the list reacts to the key diff --git a/Readme.html b/Readme.html index deea72bef..d75be99f4 100644 --- a/Readme.html +++ b/Readme.html @@ -3034,11 +3034,11 @@ current job, and their current status.

    current count is below the lower bound of the range, the job is resumed; if it is above or equal to the top bound, it will be suspended. Within the range, the specific constraint has no effect on the job; others may still affect it.

    -

    Pressing 'c' switches the current constraint between counting stacks or items. -Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the -bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting -items and expanding the range each gives a 5x bonus).

    -

    Pressing 'n' produces a list of possible outputs of this job as guessed by +

    Pressing 'I' switches the current constraint between counting stacks or items. +Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting +items and expanding the range each gives a 2x bonus).

    +

    Pressing 'A' produces a list of possible outputs of this job as guessed by workflow, and lets you create a new constraint by choosing one as template. If you don't see the choice you want in the list, it likely means you have to adjust the job material first using job item-material or gui/workshop-job, @@ -3050,6 +3050,23 @@ added to the list. If you use Shift-Enter, the interface proceeds to the next dialog, which allows you to edit the suggested constraint parameters to suit your need, and set the item count range.

    images/workflow-new2.png +

    Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen) +opens the overall status screen, which was copied from the C++ implementation +by falconne for better integration with the rest of the lua script:

    +images/workflow-status.png +

    This screen shows all currently existing workflow constraints, and allows +monitoring and/or changing them from one screen. The constraint list can +be filtered by typing text in the field below.

    +

    The color of the stock level number indicates how "healthy" the stock level +is, based on current count and trend. Bright green is very good, green is good, +red is bad, bright red is very bad.

    +

    The limit number is also color-coded. Red means that there are currently no +workshops producing that item (i.e. no jobs). If it's yellow, that means the +production has been delayed, possibly due to lack of input materials.

    +

    The chart on the right is a plot of the last 14 days (28 half day plots) worth +of stock history for the selected item, with the rightmost point representing +the current stock value. The bright green dashed line is the target +limit (maximum) and the dark green line is that minus the gap (minimum).

    gui/assign-rack

    diff --git a/Readme.rst b/Readme.rst index a214a6ecb..a84691b05 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2295,12 +2295,12 @@ current count is below the lower bound of the range, the job is resumed; if it is above or equal to the top bound, it will be suspended. Within the range, the specific constraint has no effect on the job; others may still affect it. -Pressing 'c' switches the current constraint between counting stacks or items. -Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the -bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting -items and expanding the range each gives a 5x bonus). +Pressing 'I' switches the current constraint between counting stacks or items. +Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting +items and expanding the range each gives a 2x bonus). -Pressing 'n' produces a list of possible outputs of this job as guessed by +Pressing 'A' produces a list of possible outputs of this job as guessed by workflow, and lets you create a new constraint by choosing one as template. If you don't see the choice you want in the list, it likely means you have to adjust the job material first using ``job item-material`` or ``gui/workshop-job``, @@ -2316,6 +2316,29 @@ suit your need, and set the item count range. .. image:: images/workflow-new2.png +Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen) +opens the overall status screen, which was copied from the C++ implementation +by falconne for better integration with the rest of the lua script: + +.. image:: images/workflow-status.png + +This screen shows all currently existing workflow constraints, and allows +monitoring and/or changing them from one screen. The constraint list can +be filtered by typing text in the field below. + +The color of the stock level number indicates how "healthy" the stock level +is, based on current count and trend. Bright green is very good, green is good, +red is bad, bright red is very bad. + +The limit number is also color-coded. Red means that there are currently no +workshops producing that item (i.e. no jobs). If it's yellow, that means the +production has been delayed, possibly due to lack of input materials. + +The chart on the right is a plot of the last 14 days (28 half day plots) worth +of stock history for the selected item, with the rightmost point representing +the current stock value. The bright green dashed line is the target +limit (maximum) and the dark green line is that minus the gap (minimum). + gui/assign-rack =============== diff --git a/dfhack.init-example b/dfhack.init-example index 8fafa4cf4..7617b9f6e 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -91,6 +91,7 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow +keybinding add Alt-W@overallstatus "gui/workflow status" # assign weapon racks to squads so that they can be used keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack diff --git a/images/workflow-new1.png b/images/workflow-new1.png index 50d0e1f421ac9de1abf87545c8d7ade31bd4cd4a..498fc4e80b4eb9736a00429195a9eee6dbd17d14 100644 GIT binary patch literal 6775 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 H@rwOF@G~;- literal 6862 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 Q`h($ diff --git a/images/workflow-status.png b/images/workflow-status.png new file mode 100644 index 0000000000000000000000000000000000000000..6c3d989f395b6ab73fd6fe04be55bb52673b688d GIT binary patch literal 5118 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 CU+(Jw literal 0 HcmV?d00001 diff --git a/images/workflow.png b/images/workflow.png index a0a0d4216b51bcecc99c82e4ab6e527225410501..7506c730f520d74f42f35b01034e4b083db83940 100644 GIT binary patch literal 5779 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 DZgqQZ diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ebbffbbbd..cb9e1c9be 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -418,7 +418,13 @@ List.ATTRS{ function List:init(info) self.page_top = 1 self.page_size = 1 - self:setChoices(info.choices, info.selected) + + if info.choices then + self:setChoices(info.choices, info.selected) + else + self.choices = {} + self.selected = 1 + end end function List:setChoices(choices, selected) @@ -481,6 +487,9 @@ function List:moveCursor(delta, force_cb) if cnt < 1 then self.page_top = 1 self.selected = 1 + if force_cb and self.on_select then + self.on_select(nil,nil) + end return end @@ -657,13 +666,17 @@ function FilteredList:init(info) end end self.not_found = Label{ - visible = false, + visible = true, text = info.not_found_label or 'No matches', text_pen = COLOR_LIGHTRED, frame = { l = info.icon_width, t = self.list.frame.t }, } self:addviews{ self.edit, self.list, self.not_found } - self:setChoices(info.choices, info.selected) + if info.choices then + self:setChoices(info.choices, info.selected) + else + self.choices = {} + end end function FilteredList:getChoices() diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 9a45f6554..77a87c9ce 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -130,12 +130,20 @@ RangeEditor = defclass(RangeEditor, widgets.Label) RangeEditor.ATTRS { get_cb = DEFAULT_NIL, - save_cb = DEFAULT_NIL + save_cb = DEFAULT_NIL, + keys = { + count = 'CUSTOM_SHIFT_I', + modify = 'CUSTOM_SHIFT_R', + min_dec = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', + min_inc = 'BUILDING_TRIGGER_MIN_SIZE_UP', + max_dec = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + max_inc = 'BUILDING_TRIGGER_MAX_SIZE_UP', + } } function RangeEditor:init(args) self:setText{ - { key = 'BUILDING_TRIGGER_ENABLE_CREATURE', + { key = self.keys.count, text = function() local cons = self.get_cb() or null_cons if cons.goal_by_count then @@ -145,21 +153,21 @@ function RangeEditor:init(args) end end, on_activate = self:callback('onChangeUnit') }, - { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify', + { key = self.keys.modify, text = ': Range', on_activate = self:callback('onEditRange') }, NEWLINE, ' ', - { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', - on_activate = self:callback('onIncRange', 'goal_gap', 5) }, - { key = 'BUILDING_TRIGGER_MIN_SIZE_UP', + { key = self.keys.min_dec, + on_activate = self:callback('onIncRange', 'goal_gap', 2) }, + { key = self.keys.min_inc, on_activate = self:callback('onIncRange', 'goal_gap', -1) }, { text = function() local cons = self.get_cb() or null_cons return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) end }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + { key = self.keys.max_dec, on_activate = self:callback('onIncRange', 'goal_value', -1) }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_UP', - on_activate = self:callback('onIncRange', 'goal_value', 5) }, + { key = self.keys.max_inc, + on_activate = self:callback('onIncRange', 'goal_value', 2) }, { text = function() local cons = self.get_cb() or null_cons return string.format(': Max %-4d', cons.goal_value) @@ -200,9 +208,9 @@ end function RangeEditor:onIncRange(field, delta) local cons = self.get_cb() if not cons.goal_by_count then - delta = delta * 5 + delta = delta * 2 end - cons[field] = math.max(1, cons[field] + delta) + cons[field] = math.max(1, cons[field] + delta*5) self.save_cb(cons) end @@ -295,7 +303,7 @@ function NewConstraint:init(args) } }, widgets.Label{ - frame = { l = 0, t = 13 }, + frame = { l = 0, t = 14 }, text = { 'Desired range: ', { pen = COLOR_LIGHTCYAN, @@ -311,7 +319,7 @@ function NewConstraint:init(args) } }, RangeEditor{ - frame = { l = 1, t = 15 }, + frame = { l = 1, t = 16 }, get_cb = self:cb_getfield('constraint'), save_cb = self:callback('onRangeChange'), }, @@ -353,7 +361,7 @@ function NewConstraint:postinit() end function NewConstraint:isValid() - return self.constraint.item_type >= 0 + return self.constraint.item_type >= 0 or self.constraint.is_craft end function NewConstraint:onChange() @@ -455,6 +463,59 @@ function NewConstraint:onRangeChange() cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) end +------------------------------ +-- CONSTRAINT HISTORY GRAPH -- +------------------------------ + +HistoryGraph = defclass(HistoryGraph, widgets.Widget) + +HistoryGraph.ATTRS { + frame_inset = 1, + history_pen = COLOR_CYAN, +} + +function HistoryGraph:init(info) +end + +function HistoryGraph:setData(history, bars) + self.history = history or {} + self.bars = bars or {} + + local maxval = 1 + for i,v in ipairs(self.history) do + maxval = math.max(maxval, v) + end + for i,v in ipairs(self.bars) do + maxval = math.max(maxval, v.value) + end + self.max_value = maxval +end + +function HistoryGraph:onRenderFrame(dc,rect) + dc:fill(rect.x1,rect.y1,rect.x1,rect.y2,{ch='\xb3', fg=COLOR_BROWN}) + dc:fill(rect.x1,rect.y2,rect.x2,rect.y2,{ch='\xc4', fg=COLOR_BROWN}) + dc:seek(rect.x1,rect.y1):char('\x1e', COLOR_BROWN) + dc:seek(rect.x1,rect.y2):char('\xc5', COLOR_BROWN) + dc:seek(rect.x2,rect.y2):char('\x10', COLOR_BROWN) + dc:seek(rect.x1,rect.y2-1):char('0', COLOR_BROWN) +end + +function HistoryGraph:onRenderBody(dc) + local coeff = (dc.height-1)/self.max_value + + for i,v in ipairs(self.bars) do + local y = dc.height-1-math.floor(0.5 + coeff*v.value) + dc:fill(0,y,dc.width-1,y,v.pen or {ch='-', fg=COLOR_GREEN}) + end + + local xbase = dc.width-1-#self.history + for i,v in ipairs(self.history) do + local x = xbase + i + local y = dc.height-1-math.floor(0.5 + coeff*v) + dc:seek(x,y):char('*', self.history_pen) + end +end + ------------------------------ -- GLOBAL CONSTRAINT SCREEN -- ------------------------------ @@ -478,47 +539,7 @@ function ConstraintList:init(args) self:addviews{ widgets.Panel{ - frame = { w = 31, r = 0, h = 6, t = 0 }, - frame_inset = 1, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - enabled = self:callback('isAnySelected'), - text = { - { text = function() - local cur = self:getCurConstraint() - if cur then - return string.format( - 'Currently %d (%d in use)', - current_stock(cur), - if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) - ) - else - return 'No constraint selected' - end - end } - } - }, - RangeEditor{ - frame = { l = 0, t = 2 }, - enabled = self:callback('isAnySelected'), - get_cb = self:callback('getCurConstraint'), - save_cb = self:callback('saveConstraint'), - }, - } - }, - widgets.Widget{ - active = false, - frame = { w = 1, r = 31 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Widget{ - active = false, - frame = { w = 31, r = 0, h = 1, t = 6 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Panel{ - frame = { l = 0, r = 32 }, + frame = { l = 0, r = 31 }, frame_inset = 1, on_layout = function(body) self.fwidth = body.width - (12+1+1+7+1+1+1+7) @@ -541,6 +562,7 @@ function ConstraintList:init(args) edit_pen = COLOR_LIGHTCYAN, text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, + on_select = self:callback('onSelectConstraint'), }, widgets.Label{ frame = { b = 0, h = 1 }, @@ -557,27 +579,66 @@ function ConstraintList:init(args) else return COLOR_WHITE end - end }, ', ', - { key = 'CUSTOM_SHIFT_S', text = ': Search', - on_activate = function() - self.subviews.list.edit.active = not self.subviews.list.edit.active - end, - pen = function() - if self.subviews.list.edit.active then - return COLOR_LIGHTCYAN + end }, + } + } + } + }, + widgets.Panel{ + frame = { w = 30, r = 0, h = 6, t = 0 }, + frame_inset = 1, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + enabled = self:callback('isAnySelected'), + text = { + { text = function() + local cur = self:getCurConstraint() + if cur then + return string.format( + 'Currently %d (%d in use)', + current_stock(cur), + if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) + ) else - return COLOR_WHITE + return 'No constraint selected' end end } } - } + }, + RangeEditor{ + frame = { l = 0, t = 2 }, + enabled = self:callback('isAnySelected'), + get_cb = self:callback('getCurConstraint'), + save_cb = self:callback('saveConstraint'), + keys = { + count = 'CUSTOM_SHIFT_I', + modify = 'CUSTOM_SHIFT_R', + min_dec = 'SECONDSCROLL_PAGEUP', + min_inc = 'SECONDSCROLL_PAGEDOWN', + max_dec = 'SECONDSCROLL_UP', + max_inc = 'SECONDSCROLL_DOWN', + } + }, } }, + widgets.Widget{ + active = false, + frame = { w = 1, r = 30 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + widgets.Widget{ + active = false, + frame = { w = 30, r = 0, h = 1, t = 6 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + HistoryGraph{ + view_id = 'graph', + frame = { w = 30, r = 0, t = 7, b = 0 }, + } } - self.subviews.list.edit.active = false - - self:initListChoices() + self:initListChoices(nil, args.select_token) end function stock_trend_color(cons) @@ -733,6 +794,29 @@ function ConstraintList:onDeleteConstraint() ) end +function ConstraintList:onSelectConstraint(idx,item) + local history, bars + + if item then + local cons = item.obj + local vfield = if_by_count(cons, 'cur_count', 'cur_amount') + + bars = { + { value = cons.goal_value - cons.goal_gap, pen = {ch='-', fg=COLOR_GREEN} }, + { value = cons.goal_value, pen = {ch='-', fg=COLOR_LIGHTGREEN} }, + } + + history = {} + for i,v in ipairs(cons.history or {}) do + table.insert(history, v[vfield]) + end + + table.insert(history, cons[vfield]) + end + + self.subviews.graph:setData(history, bars) +end + ------------------------------- -- WORKSHOP JOB INFO OVERLAY -- ------------------------------- @@ -772,14 +856,20 @@ function JobConstraints:init(args) widgets.Label{ frame = { l = 0, b = 0 }, text = { - { key = 'CUSTOM_N', text = ': New limit, ', + { key = 'CUSTOM_SHIFT_A', text = ': Add limit, ', on_activate = self:callback('onNewConstraint') }, - { key = 'CUSTOM_X', text = ': Delete', + { key = 'CUSTOM_SHIFT_X', text = ': Delete', enabled = self:callback('isAnySelected'), on_activate = self:callback('onDeleteConstraint') }, NEWLINE, NEWLINE, { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } + on_activate = self:callback('dismiss') }, + ' ', + { key = 'CUSTOM_SHIFT_S', text = ': Status', + on_activate = function() + local sel = self:getCurConstraint() + ConstraintList{ select_token = (sel or {}).token }:show() + end } } }, } @@ -873,22 +963,16 @@ function JobConstraints:onNewConstraint() local choices = {} for i,cons in ipairs(variants) do local itemstr = describe_item_type(cons) - local matstr = describe_material(cons) - local matflags = utils.list_bitfield_flags(cons.mat_mask) - if #matflags > 0 then - local fstr = table.concat(matflags, '/') - if matstr == 'any material' then - matstr = 'any '..fstr - else - matstr = 'any '..fstr..' '..matstr - end + local matstr,matflags = describe_material(cons) + if matflags then + matstr = matflags..' '..matstr end table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) end dlg.ListBox{ - frame_title = 'New limit', + frame_title = 'Add limit', text = 'Select one of the possible outputs:', text_pen = COLOR_WHITE, choices = choices, @@ -930,7 +1014,7 @@ end local args = {...} -if args[1] == 'list' then +if args[1] == 'status' then check_enabled(function() ConstraintList{}:show() end) else if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then From df2e9f00e15cfe1f2b1d548f9cba1f8b11e4fac7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Dec 2012 17:21:06 +0400 Subject: [PATCH 04/22] Document that search now works in the stockpile settings screen. --- NEWS | 20 ++++++++++---------- Readme.html | 9 ++++++++- Readme.rst | 12 +++++++++++- images/search-stockpile.png | Bin 0 -> 5971 bytes plugins/search.cpp | 2 +- 5 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 images/search-stockpile.png diff --git a/NEWS b/NEWS index 65c647337..4856b06c3 100644 --- a/NEWS +++ b/NEWS @@ -25,18 +25,18 @@ DFHack future 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. - - gui/workflow: a front-end for the workflow plugin. + - gui/workflow: a front-end for the workflow plugin (part inspired by falconne). - gui/assign-rack: works together with a binary patch to fix weapon racks. - gui/gm-editor: an universal editor for lots of dfhack things. - gui/companion-order: a adventure mode command interface for your companions. - New binary patches: - - armorstand-capacity - - custom-reagent-size - - deconstruct-heapfall - - deconstruct-teleport - - hospital-overstocking - - training-ammo - - weaponrack-unassign + New binary patches (for use with binpatch): + - armorstand-capacity: doubles the capacity of armor stands. + - custom-reagent-size: lets custom reactions use small amounts of inputs. + - deconstruct-heapfall: stops some items still falling on head when deconstructing. + - deconstruct-teleport: stops items from 16x16 block teleporting when deconstructing. + - hospital-overstocking: stops hospital overstocking with supplies. + - training-ammo: lets dwarves with quiver full of combat-only ammo train. + - weaponrack-unassign: fixes bug that negates work done by gui/assign-rack. Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. @@ -48,7 +48,7 @@ DFHack future 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. + Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens. New AutoMaterial plugin by falconne: Makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time you place one. diff --git a/Readme.html b/Readme.html index d75be99f4..54deb013f 100644 --- a/Readme.html +++ b/Readme.html @@ -2873,7 +2873,7 @@ directly to the main dwarf mode screen.

    AutoMaterial

    diff --git a/Readme.rst b/Readme.rst index a84691b05..afad3ef4c 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2074,7 +2074,7 @@ directly to the main dwarf mode screen. Search ====== -The search plugin adds search to the Stocks, Trading and Unit List screens. +The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens. .. image:: images/search.png @@ -2097,6 +2097,16 @@ 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. +In the stockpile screen the option only appears if the cursor is in the +rightmost list: + +.. image:: images/search-stockpile.png + +Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only +on items actually shown in the rightmost list, so it is possible to select +only fat or tallow by forbidding fats, then searching for fat/tallow, and +using Permit Fats again while the list is filtered. + AutoMaterial ============ diff --git a/images/search-stockpile.png b/images/search-stockpile.png new file mode 100644 index 0000000000000000000000000000000000000000..37a0e57cd3a74a4f4f7b80d76dcd996cc92da1fa GIT binary patch literal 5971 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 Date: Sun, 2 Dec 2012 14:43:23 +0400 Subject: [PATCH 05/22] Detect mouse press events for lua. --- Lua API.html | 5 ++++- Lua API.rst | 5 ++++- library/lua/gui.lua | 2 ++ library/modules/Screen.cpp | 14 ++++++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Lua API.html b/Lua API.html index f14239a10..aa41b8b63 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1749,7 +1749,10 @@ options; if multiple interpretations exist, the table will contain multiple keys

    Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.

    _MOUSE_L, _MOUSE_R
    -

    If the left or right mouse button is pressed.

    +

    If the left or right mouse button is being pressed.

    +
    +
    _MOUSE_L_DOWN, _MOUSE_R_DOWN
    +

    If the left or right mouse button was just pressed.

    If this method is omitted, the screen is dismissed on receival of the LEAVESCREEN key.

    diff --git a/Lua API.rst b/Lua API.rst index cedc36441..eaef74997 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1610,7 +1610,10 @@ Supported callbacks and fields are: Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience. ``_MOUSE_L, _MOUSE_R`` - If the left or right mouse button is pressed. + If the left or right mouse button is being pressed. + + ``_MOUSE_L_DOWN, _MOUSE_R_DOWN`` + If the left or right mouse button was just pressed. If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key. diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 99bf9263c..603c7ab44 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -13,6 +13,8 @@ CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} local FAKE_INPUT_KEYS = { _MOUSE_L = true, _MOUSE_R = true, + _MOUSE_L_DOWN = true, + _MOUSE_R_DOWN = true, _STRING = true, } diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index cd20bc25e..782bb317d 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -664,14 +664,24 @@ int dfhack_lua_viewscreen::do_input(lua_State *L) if (enabler && enabler->tracking_on) { - if (enabler->mouse_lbut) { + if (enabler->mouse_lbut_down) { lua_pushboolean(L, true); lua_setfield(L, -2, "_MOUSE_L"); } - if (enabler->mouse_rbut) { + if (enabler->mouse_rbut_down) { lua_pushboolean(L, true); lua_setfield(L, -2, "_MOUSE_R"); } + if (enabler->mouse_lbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_L_DOWN"); + enabler->mouse_lbut = 0; + } + if (enabler->mouse_rbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_R_DOWN"); + enabler->mouse_rbut = 0; + } } lua_call(L, 2, 0); From dc7f9f56cd9d9ac6ebda4b59027841458ecbf82c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Dec 2012 15:31:43 +0400 Subject: [PATCH 06/22] Implement a low stock level announcement as suggested by falconne. --- NEWS | 1 + plugins/workflow.cpp | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 4856b06c3..b8ad53830 100644 --- a/NEWS +++ b/NEWS @@ -43,6 +43,7 @@ DFHack future - 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. + - workflow announces when the stock level gets very low once a season. 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 12174af5c..b70fd8b0c 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -322,6 +322,7 @@ struct ItemConstraint { bool request_suspend, request_resume; bool is_active, cant_resume_reported; + int low_stock_reported; TMaterialCache material_cache; @@ -329,7 +330,7 @@ public: ItemConstraint() : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0), - is_active(false), cant_resume_reported(false) + is_active(false), cant_resume_reported(false), low_stock_reported(-1) {} int goalCount() { return config.ival(0); } @@ -349,6 +350,8 @@ public: config.ival(2) &= ~1; } + int curItemStock() { return goalByCount() ? item_count : item_amount; } + void init(const std::string &str) { config.val() = str; @@ -358,7 +361,7 @@ public: void computeRequest() { - int size = goalByCount() ? item_count : item_amount; + int size = curItemStock(); request_resume = (size <= goalCount()-goalGap()); request_suspend = (size >= goalCount()); } @@ -1323,6 +1326,20 @@ static void update_jobs_by_constraints(color_ostream &out) else if (ct->mat_mask.whole) info = bitfield_to_string(ct->mat_mask) + " " + info; + if (ct->low_stock_reported != DF_GLOBAL_VALUE(cur_season,-1)) + { + int count = ct->goalCount(), gap = ct->goalGap(); + + if (count >= gap*3 && ct->curItemStock() < std::min(gap*2, (count-gap)/2)) + { + ct->low_stock_reported = DF_GLOBAL_VALUE(cur_season,-1); + + Gui::showAnnouncement("Stock level is low: " + info, COLOR_BROWN, true); + } + else + ct->low_stock_reported = -1; + } + if (is_running != ct->is_active) { if (is_running && ct->request_resume) From 3953112eb936e7afddea09413bf6dfcefa795566 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 3 Dec 2012 19:03:07 +0100 Subject: [PATCH 07/22] dump Vegetation::t_plant, fix plant.is_burning --- library/include/modules/Vegetation.h | 22 ---------------------- library/modules/Vegetation.cpp | 25 ------------------------- plugins/plants.cpp | 4 ++-- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/library/include/modules/Vegetation.h b/library/include/modules/Vegetation.h index 89ba5ff6c..f293ec52c 100644 --- a/library/include/modules/Vegetation.h +++ b/library/include/modules/Vegetation.h @@ -40,31 +40,9 @@ namespace Vegetation { const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years -// "Simplified" copy of plant -struct t_plant { - df::language_name name; - df::plant_flags flags; - int16_t material; - df::coord pos; - int32_t grow_counter; - uint16_t temperature_1; - uint16_t temperature_2; - int32_t is_burning; - int32_t hitpoints; - int16_t update_order; - //std::vector unk1; - //int32_t unk2; - //uint16_t temperature_3; - //uint16_t temperature_4; - //uint16_t temperature_5; - // Pointer to original object, in case you want to modify it - df::plant *origin; -}; - DFHACK_EXPORT bool isValid(); DFHACK_EXPORT uint32_t getCount(); DFHACK_EXPORT df::plant * getPlant(const int32_t index); -DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out); } } #endif diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp index f7c4c9b0c..9b14a3cc0 100644 --- a/library/modules/Vegetation.cpp +++ b/library/modules/Vegetation.cpp @@ -58,28 +58,3 @@ df::plant * Vegetation::getPlant(const int32_t index) return NULL; return world->plants.all[index]; } - -bool Vegetation::copyPlant(const int32_t index, t_plant &out) -{ - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->plants.all[index]; - - out.name = out.origin->name; - out.flags = out.origin->flags; - out.material = out.origin->material; - out.pos = out.origin->pos; - out.grow_counter = out.origin->grow_counter; - out.temperature_1 = out.origin->temperature.whole; - out.temperature_2 = out.origin->temperature.fraction; - out.is_burning = out.origin->is_burning; - out.hitpoints = out.origin->hitpoints; - out.update_order = out.origin->update_order; - //out.unk1 = out.origin->anon_1; - //out.unk2 = out.origin->anon_2; - //out.temperature_3 = out.origin->temperature_unk; - //out.temperature_4 = out.origin->min_safe_temp; - //out.temperature_5 = out.origin->max_safe_temp; - return true; -} diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 5ab09868f..22e60c0d0 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -113,7 +113,7 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs if(shrubs && p->flags.bits.is_shrub || trees && !p->flags.bits.is_shrub) { if (what == do_immolate) - p->is_burning = true; + p->damage_flags.bits.is_burning = true; p->hitpoints = 0; destroyed ++; } @@ -136,7 +136,7 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs if(tree->pos.x == x && tree->pos.y == y && tree->pos.z == z) { if(what == do_immolate) - tree->is_burning = true; + tree->damage_flags.bits.is_burning = true; tree->hitpoints = 0; didit = true; break; From 0b80dff09d0c0dbb6dacd469249e738e1cf25978 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 4 Dec 2012 17:18:09 +0100 Subject: [PATCH 08/22] ruby: add d-float support --- plugins/ruby/codegen.pl | 3 +++ plugins/ruby/ruby-autogen-defs.rb | 16 ++++++++++++++++ plugins/ruby/ruby.cpp | 13 +++++++++++++ 3 files changed, 32 insertions(+) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index ff69853af..9d76c6872 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -849,6 +849,9 @@ sub render_item_number { } elsif ($subtype eq 's-float') { push @lines_rb, 'float'; return; + } elsif ($subtype eq 'd-float') { + push @lines_rb, 'double'; + return; } else { print "no render number $subtype\n"; return; diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index a3e810178..e657962d5 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -35,6 +35,9 @@ module DFHack def float Float.new end + def double + Double.new + end def bit(shift, enum=nil) BitField.new(shift, 1, enum) end @@ -237,6 +240,19 @@ module DFHack _set(0.0) end end + class Double < MemStruct + def _get + DFHack.memory_read_double(@_memaddr) + end + + def _set(v) + DFHack.memory_write_double(@_memaddr, v) + end + + def _cpp_init + _set(0.0) + end + end class BitField < MemStruct attr_accessor :_shift, :_len, :_enum def initialize(shift, len, enum=nil) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index db94ad650..d75fa2402 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -578,6 +578,11 @@ static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr) return rb_float_new(*(float*)rb_num2ulong(addr)); } +static VALUE rb_dfmemory_read_double(VALUE self, VALUE addr) +{ + return rb_float_new(*(double*)rb_num2ulong(addr)); +} + // memory writing (buffer) static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw) @@ -613,6 +618,12 @@ static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val) return Qtrue; } +static VALUE rb_dfmemory_write_double(VALUE self, VALUE addr, VALUE val) +{ + *(double*)rb_num2ulong(addr) = rb_num2dbl(val); + return Qtrue; +} + // return memory permissions at address (eg "rx", nil if unmapped) static VALUE rb_dfmemory_check(VALUE self, VALUE addr) { @@ -968,12 +979,14 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1); rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1); rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1); + rb_define_singleton_method(rb_cDFHack, "memory_read_double", RUBY_METHOD_FUNC(rb_dfmemory_read_double), 1); rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2); + rb_define_singleton_method(rb_cDFHack, "memory_write_double", RUBY_METHOD_FUNC(rb_dfmemory_write_double), 2); rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1); rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2); From 74ebe7d2070a4487f9f47377a1a992254167e298 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 4 Dec 2012 17:46:13 +0100 Subject: [PATCH 09/22] ruby: add df-static-flagarray --- plugins/ruby/codegen.pl | 15 +++++++++-- plugins/ruby/ruby-autogen-defs.rb | 45 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 9d76c6872..03017a0f5 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -698,6 +698,8 @@ sub sizeof { return 12; } elsif ($subtype eq 'df-flagarray') { return 8; + } elsif ($subtype eq 'df-static-flagarray') { + return $field->getAttribute('count'); } elsif ($subtype eq 'df-array') { return 8; # XXX 6 ? } else { @@ -913,6 +915,7 @@ sub render_item_container { my $rbmethod = join('_', split('-', $subtype)); my $tg = $item->findnodes('child::ld:item')->[0]; my $indexenum = $item->getAttribute('index-enum'); + my $count = $item->getAttribute('count'); if ($tg) { if ($rbmethod eq 'df_linked_list') { @@ -929,11 +932,19 @@ sub render_item_container { elsif ($indexenum) { $indexenum = rb_ucase($indexenum); - push @lines_rb, "$rbmethod($indexenum)"; + if ($count) { + push @lines_rb, "$rbmethod($count, $indexenum)"; + } else { + push @lines_rb, "$rbmethod($indexenum)"; + } } else { - push @lines_rb, "$rbmethod"; + if ($count) { + push @lines_rb, "$rbmethod($count)"; + } else { + push @lines_rb, "$rbmethod"; + } } } diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index e657962d5..ffd68bf1e 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -78,6 +78,9 @@ module DFHack def df_flagarray(indexenum=nil) DfFlagarray.new(indexenum) end + def df_static_flagarray(len, indexenum=nil) + DfStaticFlagarray.new(len, indexenum) + end def df_array(tglen) DfArray.new(tglen, yield) end @@ -680,6 +683,48 @@ module DFHack include Enumerable end + class DfStaticFlagarray < MemStruct + attr_accessor :_indexenum + def initialize(len, indexenum) + @len = len*8 + @_indexenum = indexenum + end + def length + @len + end + def size ; length ; end + def [](idx) + idx = _indexenum.int(idx) if _indexenum + idx += length if idx < 0 + return if idx < 0 or idx >= length + byte = DFHack.memory_read_int8(@_memaddr + idx/8) + (byte & (1 << (idx%8))) > 0 + end + def []=(idx, v) + idx = _indexenum.int(idx) if _indexenum + idx += length if idx < 0 + if idx >= length or idx < 0 + raise 'index out of bounds' + else + byte = DFHack.memory_read_int8(@_memaddr + idx/8) + if (v == nil or v == false or v == 0) + byte &= 0xff ^ (1 << (idx%8)) + else + byte |= (1 << (idx%8)) + end + DFHack.memory_write_int8(@_memaddr + idx/8, byte) + end + end + def inspect + out = "#' + end + + include Enumerable + end class DfArray < Compound attr_accessor :_tglen, :_tg def initialize(tglen, tg) From cd6eb9edd38cf919ba07b158a56656731494a6e7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Dec 2012 12:00:18 +0400 Subject: [PATCH 10/22] If training ammo is forbidden for all use, don't move it to combat chests. --- plugins/fix-armory.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index efa9350ff..5a4821b4b 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -110,7 +110,8 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) * 1. Combat ammo and ammo without any allowed use can be stored * in BOXes marked for Squad Equipment, either directly or via * containing room. No-allowed-use ammo is assumed to be reserved - * for emergency combat use, or something like that. + * for emergency combat use, or something like that; however if + * it is already stored in a training chest, it won't be moved. * 1a. If assigned to a squad position, that box can be used _only_ * for ammo assigned to that specific _squad_. Otherwise, if * multiple squads can use this room, they will store their @@ -158,8 +159,8 @@ static bool is_squad_ammo(df::item *item, df::squad *squad, bool combat, bool tr bool cs = spec->flags.bits.use_combat; bool ts = spec->flags.bits.use_training; - // no-use ammo assumed to be combat - if (((cs || !ts) && combat) || (ts && train)) + // no-use ammo assumed to fit any category + if (((cs || !ts) && combat) || ((ts || !cs) && train)) { if (binsearch_index(spec->assigned, item->id) >= 0) return true; From e1b70d171cacc74a58bbf94b0f1febe8b8cdc85d Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 11:00:19 +0100 Subject: [PATCH 11/22] ruby: tweak is_citizen test --- plugins/ruby/unit.rb | 52 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 4c638b1a9..13c3711b0 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -63,12 +63,54 @@ module DFHack } end + def unit_testflagcurse(u, flag) + return false if u.curse.rem_tags1.send(flag) + return true if u.curse.add_tags1.send(flag) + return false if u.caste < 0 + u.race_tg.caste[u.caste].flags[flag] + end + + def unit_isfortmember(u) + # RE from viewscreen_unitlistst ctor + return false if df.gamemode != :DWARF or + u.mood == :Berserk or + unit_testflagcurse(u, :CRAZED) or + unit_testflagcurse(u, :OPPOSED_TO_LIFE) or + u.unknown8.unk2 or + u.flags3.ghostly or + u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or + u.flags1.forest or + u.flags1.merchant or u.flags1.diplomat + return true if u.flags1.tame + return false if u.flags2.underworld or u.flags2.resident or + u.flags2.visitor_uninvited or u.flags2.visitor or + u.civ_id == -1 or + u.civ_id != df.ui.civ_id + true + end + + # return the page in viewscreen_unitlist where the unit would appear + def unit_category(u) + return if u.flags1.left or u.flags1.incoming + # return if hostile & unit_invisible(u) (hidden_in_ambush or caged+mapblock.hidden or caged+holder.ambush + return :Dead if u.flags1.dead + return :Dead if u.flags3.ghostly # hostile ? + return :Others if !unit_isfortmember(u) + casteflags = u.race_tg.caste[u.caste].flags if u.caste >= 0 + return :Livestock if casteflags and (casteflags[:PET] or casteflags[:PET_EXOTIC]) + return :Citizens if unit_testflagcurse(u, :CAN_SPEAK) + :Livestock + # some other stuff with ui.race_id ? (jobs only?) + end + def unit_iscitizen(u) - u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and !u.flags1.forest and - !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and - !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and - u.mood != :Berserk - # TODO check curse ; currently this should keep vampires, but may include werebeasts + unit_category(u) == :Citizens + end + + def unit_ishostile(u) + unit_category(u) == :Others and + # TODO + true end # list workers (citizen, not crazy / child / inmood / noble) From 9a6eff0370b623f36ae4e4d5f428dc9bbbe20fe0 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 13:00:33 +0100 Subject: [PATCH 12/22] deathcause: allow selection from unitlist screen --- NEWS | 1 + scripts/deathcause.rb | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index b8ad53830..9cb34fcb8 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ DFHack future - removebadthoughts: add --dry-run option - superdwarf: work in adventure mode too - tweak stable-cursor: carries cursor location from/to Build menu. + - deathcause: allow selection from the unitlist screen New tweaks: - tweak military-training: speed up melee squad training up to 10x (normally 3-5x). New scripts: diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index ab3e44a39..b85a85ac2 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -11,33 +11,40 @@ def display_death_event(e) end item = df.item_find(:selected) +unit = df.unit_find(:selected) if !item or !item.kind_of?(DFHack::ItemBodyComponent) item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } end -if !item or !item.kind_of?(DFHack::ItemBodyComponent) - puts "Please select a corpse in the loo'k' menu" -else +if item and item.kind_of?(DFHack::ItemBodyComponent) hf = item.hist_figure_id - if hf == -1 - # TODO try to retrieve info from the unit (u = item.unit_tg) - puts "Not a historical figure, cannot death find info" +elsif unit + hf = unit.hist_figure_id +end + +if not hf + puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" + +elsif hf == -1 + # TODO try to retrieve info from the unit (u = item.unit_tg) + puts "Not a historical figure, cannot death find info" + +else + histfig = df.world.history.figures.binsearch(hf) + unit = histfig ? df.unit_find(histfig.unit_id) : nil + if unit and not unit.flags1.dead + puts "#{unit.name} is not dead yet !" + else events = df.world.history.events - found = false (0...events.length).reverse_each { |i| e = events[i] if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf display_death_event(e) - found = true break end } - if not found - u = item.unit_tg - puts "#{u.name} is not dead yet !" if u and not u.flags1.dead - end end end From 126c31684ec70e149436b3799326cd8aab5891fe Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 13:43:58 +0100 Subject: [PATCH 13/22] deathcause: ghosts are dead --- scripts/deathcause.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index b85a85ac2..73e29c890 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -33,7 +33,7 @@ elsif hf == -1 else histfig = df.world.history.figures.binsearch(hf) unit = histfig ? df.unit_find(histfig.unit_id) : nil - if unit and not unit.flags1.dead + if unit and not unit.flags1.dead and not unit.flags3.ghostly puts "#{unit.name} is not dead yet !" else From 885059c887c0da9b8d56b7e5fadc7740f505873f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Dec 2012 19:00:48 +0400 Subject: [PATCH 14/22] Add a script to expose the correct season to soundsense on world load. --- NEWS | 1 + dfhack.init-example | 7 +++++++ scripts/soundsense-season.lua | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 scripts/soundsense-season.lua diff --git a/NEWS b/NEWS index 9cb34fcb8..a0e01dba1 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,7 @@ DFHack future - embark: lets you embark anywhere. - lever: list and pull fort levers from the dfhack console. - stripcaged: mark items inside cages for dumping, eg caged goblin weapons. + - soundsense-season: writes the correct season to gamelog.txt on world load. 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 7617b9f6e..1a5aee48f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -137,6 +137,13 @@ tweak military-color-assigned # remove inverse dependency of squad training speed on unit list size and use more sparring tweak military-training +########### +# Scripts # +########### + +# write the correct season to gamelog on world load +soundsense-season + ####################################################### # Apply binary patches at runtime # # # diff --git a/scripts/soundsense-season.lua b/scripts/soundsense-season.lua new file mode 100644 index 000000000..6b7d43cfa --- /dev/null +++ b/scripts/soundsense-season.lua @@ -0,0 +1,26 @@ +-- On map load writes the current season to gamelog.txt + +local seasons = { + [0] = 'Spring', + [1] = 'Summer', + [2] = 'Autumn', + [3] = 'Winter', +} + +local args = {...} + +local function write_gamelog(msg) + local log = io.open('gamelog.txt', 'a') + log:write(msg.."\n") + log:close() +end + +if args[1] == 'disable' then + dfhack.onStateChange[_ENV] = nil +else + dfhack.onStateChange[_ENV] = function(op) + if op == SC_WORLD_LOADED then + write_gamelog(seasons[df.global.cur_season]..' has arrived on the calendar.') + end + end +end From ebc2625d970eb0ff6ec871ba9b3301a7004f00c5 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 23:46:59 +0100 Subject: [PATCH 15/22] ditch the unused Vegetation module --- library/CMakeLists.txt | 2 - library/include/DFHack.h | 1 - library/include/ModuleFactory.h | 1 - library/include/modules/Maps.h | 1 - library/include/modules/Vegetation.h | 48 ---------------------- library/modules/Maps.cpp | 1 + library/modules/Vegetation.cpp | 60 ---------------------------- plugins/cleaners.cpp | 1 + plugins/devel/tiles.cpp | 1 - plugins/getplants.cpp | 2 +- plugins/liquids.cpp | 1 - plugins/mapexport/mapexport.cpp | 1 + plugins/plants.cpp | 8 ++-- plugins/prospector.cpp | 1 + plugins/tiletypes.cpp | 1 - 15 files changed, 10 insertions(+), 120 deletions(-) delete mode 100644 library/include/modules/Vegetation.h delete mode 100644 library/modules/Vegetation.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index f67b6fe44..784b54c90 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -121,7 +121,6 @@ include/modules/Materials.h include/modules/Notes.h include/modules/Screen.h include/modules/Translation.h -include/modules/Vegetation.h include/modules/Vermin.h include/modules/World.h include/modules/Graphic.h @@ -142,7 +141,6 @@ modules/Materials.cpp modules/Notes.cpp modules/Screen.cpp modules/Translation.cpp -modules/Vegetation.cpp modules/Vermin.cpp modules/World.cpp modules/Graphic.cpp diff --git a/library/include/DFHack.h b/library/include/DFHack.h index d606df94b..8a094cf86 100644 --- a/library/include/DFHack.h +++ b/library/include/DFHack.h @@ -61,7 +61,6 @@ distribution. #include "modules/Translation.h" #include "modules/World.h" #include "modules/Items.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h index 1f3d4222a..87c9a726f 100644 --- a/library/include/ModuleFactory.h +++ b/library/include/ModuleFactory.h @@ -33,7 +33,6 @@ namespace DFHack Module* createGui(); Module* createWorld(); Module* createMaterials(); - Module* createVegetation(); Module* createNotes(); Module* createGraphic(); } diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 632e8ec13..82f79e94b 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -32,7 +32,6 @@ distribution. #include "Export.h" #include "Module.h" -#include "modules/Vegetation.h" #include #include "BitArray.h" #include "modules/Materials.h" diff --git a/library/include/modules/Vegetation.h b/library/include/modules/Vegetation.h deleted file mode 100644 index f293ec52c..000000000 --- a/library/include/modules/Vegetation.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#ifndef CL_MOD_VEGETATION -#define CL_MOD_VEGETATION -/** - * \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves - * @ingroup grp_modules - */ - -#include "Export.h" -#include "DataDefs.h" -#include "df/plant.h" - -namespace DFHack -{ -namespace Vegetation -{ -const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years - -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT df::plant * getPlant(const int32_t index); -} -} -#endif diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 363de8064..38f8bfb9f 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -59,6 +59,7 @@ using namespace std; #include "df/z_level_flags.h" #include "df/region_map_entry.h" #include "df/flow_info.h" +#include "df/plant.h" using namespace DFHack; using namespace df::enums; diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp deleted file mode 100644 index 9b14a3cc0..000000000 --- a/library/modules/Vegetation.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "MemAccess.h" -#include "Types.h" -#include "Core.h" -using namespace DFHack; - -#include "modules/Vegetation.h" -#include "df/world.h" - -using namespace DFHack; -using df::global::world; - -bool Vegetation::isValid() -{ - return (world != NULL); -} - -uint32_t Vegetation::getCount() -{ - return world->plants.all.size(); -} - -df::plant * Vegetation::getPlant(const int32_t index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->plants.all[index]; -} diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 319b83c1f..1a52f8a17 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -12,6 +12,7 @@ #include "df/global_objects.h" #include "df/builtin_mats.h" #include "df/contaminant.h" +#include "df/plant.h" using std::vector; using std::string; diff --git a/plugins/devel/tiles.cpp b/plugins/devel/tiles.cpp index 1d30ca953..972b7fd0d 100644 --- a/plugins/devel/tiles.cpp +++ b/plugins/devel/tiles.cpp @@ -11,7 +11,6 @@ using std::string; #include #include #include -#include #include #include #include diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 56c8457cc..eaa8077f2 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -11,8 +11,8 @@ #include "df/map_block.h" #include "df/tile_dig_designation.h" #include "df/plant_raw.h" +#include "df/plant.h" -#include "modules/Vegetation.h" #include using std::string; diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 6df530a92..15ae84c9b 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -37,7 +37,6 @@ using std::set; #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "TileTypes.h" diff --git a/plugins/mapexport/mapexport.cpp b/plugins/mapexport/mapexport.cpp index 6bc2d6fb2..9d1ba1c1d 100644 --- a/plugins/mapexport/mapexport.cpp +++ b/plugins/mapexport/mapexport.cpp @@ -13,6 +13,7 @@ using namespace google::protobuf::io; #include "DataDefs.h" #include "df/world.h" +#include "df/plant.h" #include "modules/Constructions.h" #include "proto/Map.pb.h" diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 22e60c0d0..89a3257fa 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -9,17 +9,19 @@ #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "TileTypes.h" #include "modules/MapCache.h" +#include "df/plant.h" using std::vector; using std::string; using namespace DFHack; using df::global::world; +const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years + command_result df_grow (color_ostream &out, vector & parameters); command_result df_immolate (color_ostream &out, vector & parameters); command_result df_extirpate (color_ostream &out, vector & parameters); @@ -219,7 +221,7 @@ command_result df_grow (color_ostream &out, vector & parameters) if(tileShape(map.tiletypeAt(DFCoord(x,y,z))) == tiletype_shape::SAPLING && tileSpecial(map.tiletypeAt(DFCoord(x,y,z))) != tiletype_special::DEAD) { - tree->grow_counter = Vegetation::sapling_to_tree_threshold; + tree->grow_counter = sapling_to_tree_threshold; } break; } @@ -235,7 +237,7 @@ command_result df_grow (color_ostream &out, vector & parameters) df::tiletype ttype = map.tiletypeAt(df::coord(p->pos.x,p->pos.y,p->pos.z)); if(!p->flags.bits.is_shrub && tileShape(ttype) == tiletype_shape::SAPLING && tileSpecial(ttype) != tiletype_special::DEAD) { - p->grow_counter = Vegetation::sapling_to_tree_threshold; + p->grow_counter = sapling_to_tree_threshold; } } } diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 5eab897c0..efd457dfd 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -33,6 +33,7 @@ using namespace std; #include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" +#include "df/plant.h" using namespace DFHack; using namespace df::enums; diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 6af94f2ee..a48b1a385 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -34,7 +34,6 @@ using std::set; #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "TileTypes.h" From a1eeb02a1b0fc9ae13615089bdd314fe6402350b Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 7 Dec 2012 01:16:40 +0100 Subject: [PATCH 16/22] autocomplete command names from the console --- library/Core.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index fd96d5601..7e9c90e98 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -343,6 +343,50 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } +static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) +{ + std::vector possible; + + auto plug_mgr = Core::getInstance().getPluginManager(); + for(size_t i = 0; i < plug_mgr->size(); i++) + { + const Plugin * plug = (plug_mgr->operator[](i)); + for (size_t j = 0; j < plug->size(); j++) + { + const PluginCommand &pcmd = plug->operator[](j); + if (pcmd.isHotkeyCommand()) + continue; + if (pcmd.name.substr(0, first.size()) == first) + possible.push_back(pcmd.name); + } + } + + bool all = (first.find('/') != std::string::npos); + + std::map scripts; + listScripts(plug_mgr, scripts, Core::getInstance().getHackPath() + "scripts/", all); + for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) + if (iter->first.substr(0, first.size()) == first) + possible.push_back(iter->first); + + if (possible.size() == 1) + { + completed = possible[0]; + fprintf(stderr, "Autocompleted %s to %s\n", first.c_str(), completed.c_str()); + return true; + } + + if (possible.size() > 1 && possible.size() < 8) + { + std::string out; + for (size_t i = 0; i < possible.size(); i++) + out += " " + possible[i]; + con.print("Possible completions:%s\n", out.c_str()); + } + + return false; +} + command_result Core::runCommand(color_ostream &con, const std::string &first, vector &parts) { if (!first.empty()) @@ -665,10 +709,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve if(res == CR_NOT_IMPLEMENTED) { auto filename = getHackPath() + "scripts/" + first; + std::string completed; + if (fileExists(filename + ".lua")) res = runLuaScript(con, first, parts); else if (plug_mgr->eval_ruby && fileExists(filename + ".rb")) res = runRubyScript(con, plug_mgr, first, parts); + else if (try_autocomplete(con, first, completed)) + return runCommand(con, completed, parts); else con.printerr("%s is not a recognized command.\n", first.c_str()); } @@ -733,7 +781,6 @@ void fIOthread(void * iodata) { string command = ""; int ret = con.lineedit("[DFHack]# ",command, main_history); - fprintf(stderr,"Command: [%s]\n",command.c_str()); if(ret == -2) { cerr << "Console is shutting down properly." << endl; @@ -747,14 +794,10 @@ void fIOthread(void * iodata) else if(ret) { // a proper, non-empty command was entered - fprintf(stderr,"Adding command to history\n"); main_history.add(command); - fprintf(stderr,"Saving history\n"); main_history.save("dfhack.history"); } - fprintf(stderr,"Running command\n"); - auto rv = core->runCommand(con, command); if (rv == CR_NOT_IMPLEMENTED) From 99e9785826f9f12a513c4fc66d5d0ffe7bf1963d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 7 Dec 2012 18:10:24 +0400 Subject: [PATCH 17/22] Add a script for inspecting screen tile parameters. --- scripts/devel/inspect-screen.lua | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 scripts/devel/inspect-screen.lua diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua new file mode 100644 index 000000000..1cc24c976 --- /dev/null +++ b/scripts/devel/inspect-screen.lua @@ -0,0 +1,101 @@ +-- Read the tiles from the screen and display info about them. + +local utils = require 'utils' +local gui = require 'gui' + +InspectScreen = defclass(InspectScreen, gui.Screen) + +function InspectScreen:init(args) + local w,h = dfhack.screen.getWindowSize() + self.cursor_x = math.floor(w/2) + self.cursor_y = math.floor(h/2) +end + +function InspectScreen:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + self.cursor_x = math.max(0, math.min(self.cursor_x, sw-1)) + self.cursor_y = math.max(0, math.min(self.cursor_y, sh-1)) + + local frame = { w = 14, r = 1, h = 10, t = 1 } + if self.cursor_x > sw/2 then + frame = { w = 14, l = 1, h = 10, t = 1 } + end + + return gui.compute_frame_body(sw, sh, frame, 1, 0, false) +end + +function InspectScreen:onRenderFrame(dc, rect) + self:renderParent() + self.cursor_pen = dfhack.screen.readTile(self.cursor_x, self.cursor_y) + if gui.blink_visible(100) then + dfhack.screen.paintTile({ch='X',fg=COLOR_LIGHTGREEN}, self.cursor_x, self.cursor_y) + end + dc:fill(rect, {ch=' ',fg=COLOR_BLACK,bg=COLOR_CYAN}) +end + +local FG_PEN = {fg=COLOR_WHITE,bg=COLOR_BLACK,tile_color=true} +local BG_PEN = {fg=COLOR_BLACK,bg=COLOR_WHITE,tile_color=true} +local TXT_PEN = {fg=COLOR_WHITE} + +function InspectScreen:onRenderBody(dc) + dc:pen(COLOR_WHITE, COLOR_CYAN) + if self.cursor_pen then + local info = self.cursor_pen + dc:string('CH: '):char(info.ch, FG_PEN):char(info.ch, BG_PEN):string(' '):string(''..info.ch,TXT_PEN):newline() + local fgcolor = info.fg + local fgstr = info.fg + if info.bold then + fgcolor = (fgcolor+8)%16 + fgstr = fgstr..'+8' + end + dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN):newline() + dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN):newline() + local bstring = 'false' + if info.bold then bstring = 'true' end + dc:string('Bold: '..bstring):newline():newline() + + if info.tile and gui.USE_GRAPHICS then + dc:string('TL: '):tile(' ', info.tile, FG_PEN):tile(' ', info.tile, BG_PEN):string(' '..info.tile):newline() + if info.tile_color then + dc:string('Color: true') + elseif info.tile_fg then + dc:string('FG: '):string('NN',{fg=info.tile_fg}):string(' '):string(''..info.tile_fg,TXT_PEN):newline() + dc:string('BG: '):string('NN',{fg=info.tile_bg}):string(' '):string(''..info.tile_bg,TXT_PEN):newline() + end + end + else + dc:string('Invalid', COLOR_LIGHTRED) + end +end + +local MOVEMENT_KEYS = { + CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 }, + CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 }, + CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 }, + CURSOR_DOWNLEFT = { -1, 1, 0 }, CURSOR_DOWNRIGHT = { 1, 1, 0 }, + CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true }, + CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true }, + CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true }, + CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true }, +} + +function InspectScreen:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + else + for k,v in pairs(MOVEMENT_KEYS) do + if keys[k] then + local delta = 1 + if v[4] then + delta = 10 + end + self.cursor_x = self.cursor_x + delta*v[1] + self.cursor_y = self.cursor_y + delta*v[2] + self:updateLayout() + return + end + end + end +end + +InspectScreen{}:show() From 6fd306b5586aa1b90c83d6904fe5d1d65795b18e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 09:51:09 +0400 Subject: [PATCH 18/22] Add tiles colored separately by fg and bg in inspect-screen. --- scripts/devel/inspect-screen.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua index 1cc24c976..a4fbafbbc 100644 --- a/scripts/devel/inspect-screen.lua +++ b/scripts/devel/inspect-screen.lua @@ -48,8 +48,10 @@ function InspectScreen:onRenderBody(dc) fgcolor = (fgcolor+8)%16 fgstr = fgstr..'+8' end - dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN):newline() - dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN):newline() + dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN) + dc:seek(dc.width-1):char(info.ch,{fg=info.fg,bold=info.bold}):newline() + dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN) + dc:seek(dc.width-1):char(info.ch,{fg=COLOR_BLACK,bg=info.bg}):newline() local bstring = 'false' if info.bold then bstring = 'true' end dc:string('Bold: '..bstring):newline():newline() From 7307f4e870d7b9381f5000242d7c2726ffaace44 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 09:51:35 +0400 Subject: [PATCH 19/22] Fix crash and confusing behavior in automaterial. --- plugins/automaterial.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 9f383b935..6f613cf0e 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -148,8 +148,8 @@ static MaterialDescriptor get_material_in_list(size_t i) } else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) { - result.item_type = gen->item_type; - result.item_subtype = gen->item_subtype; + result.item_type = spec->candidate->getType(); + result.item_subtype = spec->candidate->getSubtype(); result.type = spec->candidate->getActualMaterial(); result.index = spec->candidate->getActualMaterialIndex(); result.valid = true; @@ -294,7 +294,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { if (in_material_choice_stage()) { - if (!last_used_moved) + if (!last_used_moved && ui_build_selector->is_grouped) { if (auto_choose_materials && get_curr_constr_prefs().size() > 0) { @@ -304,7 +304,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } } - else if (ui_build_selector->is_grouped) + else { last_used_moved = true; move_material_to_top(get_last_used_material()); From 10667dfb9ee09c664959499257bca8cfbfed4e64 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 14:45:17 +0400 Subject: [PATCH 20/22] Make the inspect screen background look more sane on some tilesets. Namely where ' ' is not totally transparent. --- scripts/devel/inspect-screen.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua index a4fbafbbc..ae8334ad7 100644 --- a/scripts/devel/inspect-screen.lua +++ b/scripts/devel/inspect-screen.lua @@ -30,7 +30,7 @@ function InspectScreen:onRenderFrame(dc, rect) if gui.blink_visible(100) then dfhack.screen.paintTile({ch='X',fg=COLOR_LIGHTGREEN}, self.cursor_x, self.cursor_y) end - dc:fill(rect, {ch=' ',fg=COLOR_BLACK,bg=COLOR_CYAN}) + dc:fill(rect, {ch=' ',fg=COLOR_WHITE,bg=COLOR_CYAN}) end local FG_PEN = {fg=COLOR_WHITE,bg=COLOR_BLACK,tile_color=true} From a0e671d75d19a1a1728a7d0d2c4c174ce66acf60 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 20:39:57 +0400 Subject: [PATCH 21/22] Make rename unit reset the name if it becomes completely empty. --- library/modules/Translation.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 6f4ca2b04..90f8bbb81 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -115,6 +115,9 @@ void Translation::setNickname(df::language_name *name, std::string nick) if (!name->has_name) { + if (nick.empty()) + return; + *name = df::language_name(); name->language = 0; @@ -122,6 +125,18 @@ void Translation::setNickname(df::language_name *name, std::string nick) } name->nickname = nick; + + // If the nick is empty, check if this made the whole name empty + if (name->nickname.empty() && name->first_name.empty()) + { + bool has_words = false; + for (int i = 0; i < 7; i++) + if (name->words[i] >= 0) + has_words = true; + + if (!has_words) + name->has_name = false; + } } string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) From 2018ac1d1751563f208399a0e898b535393a1228 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 21:25:16 -0600 Subject: [PATCH 22/22] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 22b01b80a..506ab1e68 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit 506ab1e68d1522e2f282f134176b7da774f6a73c