From f2958a552901c5c04505af8b28f1638bd535868b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 28 Mar 2023 23:51:52 -0700 Subject: [PATCH] implement automaterial selection for buildingplan --- docs/changelog.txt | 1 + plugins/buildingplan/buildingplan.cpp | 6 +- plugins/buildingplan/buildingplan.h | 6 ++ plugins/buildingplan/defaultitemfilters.cpp | 10 ++-- plugins/buildingplan/defaultitemfilters.h | 6 +- plugins/lua/buildingplan/itemselection.lua | 61 +++++++++++++++++---- plugins/lua/buildingplan/planneroverlay.lua | 43 +++++++++++---- 7 files changed, 100 insertions(+), 33 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index f09fa8cc1..49c5bc0b5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -53,6 +53,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - `buildingplan`: filters and global settings are now ignored when manually choosing items for a building - `buildingplan`: if `suspendmanager` is running, then planned buildings will be left suspended when their items are all attached. `suspendmanager` will unsuspsend them for construction when it is safe to do so. +- `buildingplan`: add option for autoselecting the last manually chosen item (like `automaterial` used to do) - `confirm`: adds confirmation for removing burrows via the repaint menu - `stockpiles`: support applying stockpile configurations with fully enabled categories to stockpiles in worlds other than the one where the configuration was exported from - `stockpiles`: support partial application of a saved config based on dynamic filtering diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 11e08c2a0..7debbbf79 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -938,8 +938,10 @@ static int getMaterialFilter(lua_State *L) { return 1; } -static void setChooseItems(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, bool choose) { - DEBUG(status,out).print("entering setChooseItems\n"); +static void setChooseItems(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int choose) { + DEBUG(status,out).print( + "entering setChooseItems building_type=%d subtype=%d custom=%d choose=%d\n", + type, subtype, custom, choose); BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(out, key); filters.setChooseItems(choose); diff --git a/plugins/buildingplan/buildingplan.h b/plugins/buildingplan/buildingplan.h index 26aa77fbc..5c4f25aa4 100644 --- a/plugins/buildingplan/buildingplan.h +++ b/plugins/buildingplan/buildingplan.h @@ -42,6 +42,12 @@ enum HeatSafety { HEAT_SAFETY_MAGMA = 2, }; +enum ItemSelectionChoice { + ITEM_SELECTION_CHOICE_FILTER = 0, + ITEM_SELECTION_CHOICE_MANUAL = 1, + ITEM_SELECTION_CHOICE_AUTOMATERIAL = 2, +}; + int get_config_val(DFHack::PersistentDataItem &c, int index); bool get_config_bool(DFHack::PersistentDataItem &c, int index); void set_config_val(DFHack::PersistentDataItem &c, int index, int value); diff --git a/plugins/buildingplan/defaultitemfilters.cpp b/plugins/buildingplan/defaultitemfilters.cpp index ac0a4699e..35147579c 100644 --- a/plugins/buildingplan/defaultitemfilters.cpp +++ b/plugins/buildingplan/defaultitemfilters.cpp @@ -39,14 +39,14 @@ static string serialize(const std::vector &item_filters, const std:: } DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector &jitems) - : key(key), choose_items(false) { + : key(key), choose_items(ItemSelectionChoice::ITEM_SELECTION_CHOICE_FILTER) { DEBUG(status,out).print("creating persistent data for filter key %d,%d,%d\n", std::get<0>(key), std::get<1>(key), std::get<2>(key)); filter_config = World::AddPersistentData(FILTER_CONFIG_KEY); set_config_val(filter_config, FILTER_CONFIG_TYPE, std::get<0>(key)); set_config_val(filter_config, FILTER_CONFIG_SUBTYPE, std::get<1>(key)); set_config_val(filter_config, FILTER_CONFIG_CUSTOM, std::get<2>(key)); - set_config_bool(filter_config, FILTER_CONFIG_CHOOSE_ITEMS, choose_items); + set_config_val(filter_config, FILTER_CONFIG_CHOOSE_ITEMS, choose_items); item_filters.resize(jitems.size()); for (size_t idx = 0; idx < jitems.size(); ++idx) { item_filters[idx].setMaxQuality(get_max_quality(jitems[idx]), true); @@ -56,7 +56,7 @@ DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &filter_config, const std::vector &jitems) : key(getKey(filter_config)), filter_config(filter_config) { - choose_items = get_config_bool(filter_config, FILTER_CONFIG_CHOOSE_ITEMS); + choose_items = get_config_val(filter_config, FILTER_CONFIG_CHOOSE_ITEMS); auto &serialized = filter_config.val(); DEBUG(status,out).print("deserializing default item filters for key %d,%d,%d: %s\n", std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str()); @@ -81,9 +81,9 @@ DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &f } } -void DefaultItemFilters::setChooseItems(bool choose) { +void DefaultItemFilters::setChooseItems(int choose) { choose_items = choose; - set_config_bool(filter_config, FILTER_CONFIG_CHOOSE_ITEMS, choose); + set_config_val(filter_config, FILTER_CONFIG_CHOOSE_ITEMS, choose); } void DefaultItemFilters::setSpecial(const std::string &special, bool val) { diff --git a/plugins/buildingplan/defaultitemfilters.h b/plugins/buildingplan/defaultitemfilters.h index d7ed12a7b..7d285ce4c 100644 --- a/plugins/buildingplan/defaultitemfilters.h +++ b/plugins/buildingplan/defaultitemfilters.h @@ -14,17 +14,17 @@ public: DefaultItemFilters(DFHack::color_ostream &out, BuildingTypeKey key, const std::vector &jitems); DefaultItemFilters(DFHack::color_ostream &out, DFHack::PersistentDataItem &filter_config, const std::vector &jitems); - void setChooseItems(bool choose); + void setChooseItems(int choose); void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index); void setSpecial(const std::string &special, bool val); - bool getChooseItems() const { return choose_items; } + int getChooseItems() const { return choose_items; } const std::vector & getItemFilters() const { return item_filters; } const std::set & getSpecials() const { return specials; } private: DFHack::PersistentDataItem filter_config; - bool choose_items; + int choose_items; std::vector item_filters; std::set specials; }; diff --git a/plugins/lua/buildingplan/itemselection.lua b/plugins/lua/buildingplan/itemselection.lua index 8134b9455..9cfe0f843 100644 --- a/plugins/lua/buildingplan/itemselection.lua +++ b/plugins/lua/buildingplan/itemselection.lua @@ -15,6 +15,12 @@ local BUILD_TEXT_HPEN = to_pen{fg=COLOR_WHITE, bg=COLOR_GREEN, keep_lower=true} -- most recent entries are at the *end* of the list local recently_used = {} +function get_automaterial_selection(building_type) + local tracker = recently_used[building_type] + if not tracker or not tracker.list then return end + return tracker.list[#tracker.list] +end + local function sort_by_type(a, b) local ad, bd = a.data, b.data return ad.item_type < bd.item_type or @@ -54,6 +60,7 @@ ItemSelection.ATTRS{ index=DEFAULT_NIL, desc=DEFAULT_NIL, quantity=DEFAULT_NIL, + autoselect=DEFAULT_NIL, on_submit=DEFAULT_NIL, on_cancel=DEFAULT_NIL, } @@ -63,34 +70,55 @@ function ItemSelection:init() self.selected_set = {} local plural = self.quantity == 1 and '' or 's' + local choices = self:get_choices(sort_by_recency) + + if self.autoselect then + self:do_autoselect(choices) + if self.num_selected >= self.quantity then + self:submit(choices) + return + end + end + self:addviews{ widgets.Label{ - frame={t=0, l=0, r=10}, + frame={t=0, l=0, r=16}, text={ - self.desc, - plural, - NEWLINE, + self.desc, plural, NEWLINE, ('Select up to %d item%s ('):format(self.quantity, plural), {text=function() return self.num_selected end}, ' selected)', }, }, widgets.Label{ - frame={r=0, w=11, t=0, h=3}, + frame={r=0, w=15, t=0, h=3}, text_pen=BUILD_TEXT_PEN, text_hpen=BUILD_TEXT_HPEN, text={ - ' ', NEWLINE, - ' Confirm ', NEWLINE, - ' ', + ' Use filter ', NEWLINE, + ' for remaining ', NEWLINE, + ' items ', }, on_click=self:callback('submit'), + visible=function() return self.num_selected < self.quantity end, + }, + widgets.Label{ + frame={r=0, w=15, t=0, h=3}, + text_pen=BUILD_TEXT_PEN, + text_hpen=BUILD_TEXT_HPEN, + text={ + ' ', NEWLINE, + ' Continue ', NEWLINE, + ' ', + }, + on_click=self:callback('submit'), + visible=function() return self.num_selected >= self.quantity end, }, widgets.FilteredList{ view_id='flist', frame={t=3, l=0, r=0, b=4}, case_sensitive=false, - choices=self:get_choices(sort_by_recency), + choices=choices, icon_width=2, on_submit=self:callback('toggle_group'), edit_on_char=function(ch) return ch:match('[%l -]') end, @@ -116,7 +144,7 @@ function ItemSelection:init() widgets.HotkeyLabel{ frame={l=22, b=1}, key='CUSTOM_SHIFT_C', - label='Confirm', + label='Continue', auto_width=true, on_activate=self:callback('submit'), }, @@ -215,6 +243,13 @@ function ItemSelection:get_choices(sort_fn) return choices end +function ItemSelection:do_autoselect(choices) + if #choices == 0 then return end + local desired = get_automaterial_selection(uibs.building_type) + if choices[1].search_key ~= desired then return end + self:toggle_group(1, choices[1]) +end + function ItemSelection:increment_group(idx, choice) local data = choice.data if self.quantity <= self.num_selected then return false end @@ -282,13 +317,13 @@ local function track_recently_used(choices) end end -function ItemSelection:submit() +function ItemSelection:submit(choices) local selected_items = {} for item_id in pairs(self.selected_set) do table.insert(selected_items, item_id) end if #selected_items > 0 then - track_recently_used(self.subviews.flist:getChoices()) + track_recently_used(choices or self.subviews.flist:getChoices()) end self.on_submit(selected_items) end @@ -328,6 +363,7 @@ ItemSelectionScreen.ATTRS { index=DEFAULT_NIL, desc=DEFAULT_NIL, quantity=DEFAULT_NIL, + autoselect=DEFAULT_NIL, on_submit=DEFAULT_NIL, on_cancel=DEFAULT_NIL, } @@ -338,6 +374,7 @@ function ItemSelectionScreen:init() index=self.index, desc=self.desc, quantity=self.quantity, + autoselect=self.autoselect, on_submit=self.on_submit, on_cancel=self.on_cancel, } diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index 1ae7885cb..3c08597d3 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -490,12 +490,21 @@ function PlannerOverlay:init() }, widgets.CycleHotkeyLabel{ view_id='choose', - frame={b=0, l=0, w=25}, + frame={b=0, l=0}, key='CUSTOM_I', - label='Choose from items:', - options={{label='Yes', value=true}, - {label='No', value=false}}, - initial_option=false, + label='Item selection:', + options={ + {label='Use filters', value=0}, + { + label=function() + local automaterial = itemselection.get_automaterial_selection(uibs.building_type) + return ('Last choice (%s)'):format(automaterial or 'Will ask') + end, + value=2, + }, + {label='Manual choice', value=1}, + }, + initial_option=0, on_change=function(choose) buildingplan.setChooseItems(uibs.building_type, uibs.building_subtype, uibs.custom_type, choose) end, @@ -670,10 +679,11 @@ function PlannerOverlay:onInput(keys) if is_choosing_area() or cur_building_has_no_area() then local filters = get_cur_filters() local num_filters = #filters - local choose = self.subviews.choose - if choose:getOptionValue() then + local choose = self.subviews.choose:getOptionValue() + if choose > 0 then local bounds = get_selected_bounds() self:save_placement() + local autoselect = choose == 2 local is_hollow = self.subviews.hollow:getOptionValue() local chosen_items, active_screens = {}, {} local pending = num_filters @@ -681,14 +691,19 @@ function PlannerOverlay:onInput(keys) for idx = num_filters,1,-1 do chosen_items[idx] = {} local filter = filters[idx] - active_screens[idx] = itemselection.ItemSelectionScreen{ + local selection_screen = itemselection.ItemSelectionScreen{ index=idx, desc=require('plugins.buildingplan').get_desc(filter), quantity=get_quantity(filter, is_hollow, bounds), + autoselect=autoselect, on_submit=function(items) chosen_items[idx] = items - active_screens[idx]:dismiss() - active_screens[idx] = nil + if active_screens[idx] then + active_screens[idx]:dismiss() + active_screens[idx] = nil + else + active_screens[idx] = true + end pending = pending - 1 if pending == 0 then df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT @@ -702,7 +717,13 @@ function PlannerOverlay:onInput(keys) df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT self:restore_placement() end, - }:show() + } + if active_screens[idx] then + -- we've already returned via autoselect + active_screens[idx] = nil + else + active_screens[idx] = selection_screen:show() + end end else self:place_building(get_placement_data())