implement automaterial selection for buildingplan

develop
Myk Taylor 2023-03-28 23:51:52 -07:00
parent badafff0bc
commit f2958a5529
No known key found for this signature in database
7 changed files with 100 additions and 33 deletions

@ -53,6 +53,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Misc Improvements ## Misc Improvements
- `buildingplan`: filters and global settings are now ignored when manually choosing items for a building - `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`: 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 - `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 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 - `stockpiles`: support partial application of a saved config based on dynamic filtering

@ -938,8 +938,10 @@ static int getMaterialFilter(lua_State *L) {
return 1; return 1;
} }
static void setChooseItems(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, bool choose) { static void setChooseItems(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int choose) {
DEBUG(status,out).print("entering setChooseItems\n"); 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); BuildingTypeKey key(type, subtype, custom);
auto &filters = get_item_filters(out, key); auto &filters = get_item_filters(out, key);
filters.setChooseItems(choose); filters.setChooseItems(choose);

@ -42,6 +42,12 @@ enum HeatSafety {
HEAT_SAFETY_MAGMA = 2, 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); int get_config_val(DFHack::PersistentDataItem &c, int index);
bool get_config_bool(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); void set_config_val(DFHack::PersistentDataItem &c, int index, int value);

@ -39,14 +39,14 @@ static string serialize(const std::vector<ItemFilter> &item_filters, const std::
} }
DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems) DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &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", 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)); std::get<0>(key), std::get<1>(key), std::get<2>(key));
filter_config = World::AddPersistentData(FILTER_CONFIG_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_TYPE, std::get<0>(key));
set_config_val(filter_config, FILTER_CONFIG_SUBTYPE, std::get<1>(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_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()); item_filters.resize(jitems.size());
for (size_t idx = 0; idx < jitems.size(); ++idx) { for (size_t idx = 0; idx < jitems.size(); ++idx) {
item_filters[idx].setMaxQuality(get_max_quality(jitems[idx]), true); 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<const df::job_item *> &jitems) DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems)
: key(getKey(filter_config)), filter_config(filter_config) { : 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(); auto &serialized = filter_config.val();
DEBUG(status,out).print("deserializing default item filters for key %d,%d,%d: %s\n", 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()); 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; 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) { void DefaultItemFilters::setSpecial(const std::string &special, bool val) {

@ -14,17 +14,17 @@ public:
DefaultItemFilters(DFHack::color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems); DefaultItemFilters(DFHack::color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems);
DefaultItemFilters(DFHack::color_ostream &out, DFHack::PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems); DefaultItemFilters(DFHack::color_ostream &out, DFHack::PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems);
void setChooseItems(bool choose); void setChooseItems(int choose);
void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index); void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index);
void setSpecial(const std::string &special, bool val); void setSpecial(const std::string &special, bool val);
bool getChooseItems() const { return choose_items; } int getChooseItems() const { return choose_items; }
const std::vector<ItemFilter> & getItemFilters() const { return item_filters; } const std::vector<ItemFilter> & getItemFilters() const { return item_filters; }
const std::set<std::string> & getSpecials() const { return specials; } const std::set<std::string> & getSpecials() const { return specials; }
private: private:
DFHack::PersistentDataItem filter_config; DFHack::PersistentDataItem filter_config;
bool choose_items; int choose_items;
std::vector<ItemFilter> item_filters; std::vector<ItemFilter> item_filters;
std::set<std::string> specials; std::set<std::string> specials;
}; };

@ -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 -- most recent entries are at the *end* of the list
local recently_used = {} 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 function sort_by_type(a, b)
local ad, bd = a.data, b.data local ad, bd = a.data, b.data
return ad.item_type < bd.item_type or return ad.item_type < bd.item_type or
@ -54,6 +60,7 @@ ItemSelection.ATTRS{
index=DEFAULT_NIL, index=DEFAULT_NIL,
desc=DEFAULT_NIL, desc=DEFAULT_NIL,
quantity=DEFAULT_NIL, quantity=DEFAULT_NIL,
autoselect=DEFAULT_NIL,
on_submit=DEFAULT_NIL, on_submit=DEFAULT_NIL,
on_cancel=DEFAULT_NIL, on_cancel=DEFAULT_NIL,
} }
@ -63,34 +70,55 @@ function ItemSelection:init()
self.selected_set = {} self.selected_set = {}
local plural = self.quantity == 1 and '' or 's' 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{ self:addviews{
widgets.Label{ widgets.Label{
frame={t=0, l=0, r=10}, frame={t=0, l=0, r=16},
text={ text={
self.desc, self.desc, plural, NEWLINE,
plural,
NEWLINE,
('Select up to %d item%s ('):format(self.quantity, plural), ('Select up to %d item%s ('):format(self.quantity, plural),
{text=function() return self.num_selected end}, {text=function() return self.num_selected end},
' selected)', ' selected)',
}, },
}, },
widgets.Label{ 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_pen=BUILD_TEXT_PEN,
text_hpen=BUILD_TEXT_HPEN, text_hpen=BUILD_TEXT_HPEN,
text={ text={
' ', NEWLINE, ' Use filter ', NEWLINE,
' Confirm ', NEWLINE, ' for remaining ', NEWLINE,
' ', ' items ',
}, },
on_click=self:callback('submit'), 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{ widgets.FilteredList{
view_id='flist', view_id='flist',
frame={t=3, l=0, r=0, b=4}, frame={t=3, l=0, r=0, b=4},
case_sensitive=false, case_sensitive=false,
choices=self:get_choices(sort_by_recency), choices=choices,
icon_width=2, icon_width=2,
on_submit=self:callback('toggle_group'), on_submit=self:callback('toggle_group'),
edit_on_char=function(ch) return ch:match('[%l -]') end, edit_on_char=function(ch) return ch:match('[%l -]') end,
@ -116,7 +144,7 @@ function ItemSelection:init()
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={l=22, b=1}, frame={l=22, b=1},
key='CUSTOM_SHIFT_C', key='CUSTOM_SHIFT_C',
label='Confirm', label='Continue',
auto_width=true, auto_width=true,
on_activate=self:callback('submit'), on_activate=self:callback('submit'),
}, },
@ -215,6 +243,13 @@ function ItemSelection:get_choices(sort_fn)
return choices return choices
end 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) function ItemSelection:increment_group(idx, choice)
local data = choice.data local data = choice.data
if self.quantity <= self.num_selected then return false end if self.quantity <= self.num_selected then return false end
@ -282,13 +317,13 @@ local function track_recently_used(choices)
end end
end end
function ItemSelection:submit() function ItemSelection:submit(choices)
local selected_items = {} local selected_items = {}
for item_id in pairs(self.selected_set) do for item_id in pairs(self.selected_set) do
table.insert(selected_items, item_id) table.insert(selected_items, item_id)
end end
if #selected_items > 0 then if #selected_items > 0 then
track_recently_used(self.subviews.flist:getChoices()) track_recently_used(choices or self.subviews.flist:getChoices())
end end
self.on_submit(selected_items) self.on_submit(selected_items)
end end
@ -328,6 +363,7 @@ ItemSelectionScreen.ATTRS {
index=DEFAULT_NIL, index=DEFAULT_NIL,
desc=DEFAULT_NIL, desc=DEFAULT_NIL,
quantity=DEFAULT_NIL, quantity=DEFAULT_NIL,
autoselect=DEFAULT_NIL,
on_submit=DEFAULT_NIL, on_submit=DEFAULT_NIL,
on_cancel=DEFAULT_NIL, on_cancel=DEFAULT_NIL,
} }
@ -338,6 +374,7 @@ function ItemSelectionScreen:init()
index=self.index, index=self.index,
desc=self.desc, desc=self.desc,
quantity=self.quantity, quantity=self.quantity,
autoselect=self.autoselect,
on_submit=self.on_submit, on_submit=self.on_submit,
on_cancel=self.on_cancel, on_cancel=self.on_cancel,
} }

@ -490,12 +490,21 @@ function PlannerOverlay:init()
}, },
widgets.CycleHotkeyLabel{ widgets.CycleHotkeyLabel{
view_id='choose', view_id='choose',
frame={b=0, l=0, w=25}, frame={b=0, l=0},
key='CUSTOM_I', key='CUSTOM_I',
label='Choose from items:', label='Item selection:',
options={{label='Yes', value=true}, options={
{label='No', value=false}}, {label='Use filters', value=0},
initial_option=false, {
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) on_change=function(choose)
buildingplan.setChooseItems(uibs.building_type, uibs.building_subtype, uibs.custom_type, choose) buildingplan.setChooseItems(uibs.building_type, uibs.building_subtype, uibs.custom_type, choose)
end, end,
@ -670,10 +679,11 @@ function PlannerOverlay:onInput(keys)
if is_choosing_area() or cur_building_has_no_area() then if is_choosing_area() or cur_building_has_no_area() then
local filters = get_cur_filters() local filters = get_cur_filters()
local num_filters = #filters local num_filters = #filters
local choose = self.subviews.choose local choose = self.subviews.choose:getOptionValue()
if choose:getOptionValue() then if choose > 0 then
local bounds = get_selected_bounds() local bounds = get_selected_bounds()
self:save_placement() self:save_placement()
local autoselect = choose == 2
local is_hollow = self.subviews.hollow:getOptionValue() local is_hollow = self.subviews.hollow:getOptionValue()
local chosen_items, active_screens = {}, {} local chosen_items, active_screens = {}, {}
local pending = num_filters local pending = num_filters
@ -681,14 +691,19 @@ function PlannerOverlay:onInput(keys)
for idx = num_filters,1,-1 do for idx = num_filters,1,-1 do
chosen_items[idx] = {} chosen_items[idx] = {}
local filter = filters[idx] local filter = filters[idx]
active_screens[idx] = itemselection.ItemSelectionScreen{ local selection_screen = itemselection.ItemSelectionScreen{
index=idx, index=idx,
desc=require('plugins.buildingplan').get_desc(filter), desc=require('plugins.buildingplan').get_desc(filter),
quantity=get_quantity(filter, is_hollow, bounds), quantity=get_quantity(filter, is_hollow, bounds),
autoselect=autoselect,
on_submit=function(items) on_submit=function(items)
chosen_items[idx] = items chosen_items[idx] = items
active_screens[idx]:dismiss() if active_screens[idx] then
active_screens[idx] = nil active_screens[idx]:dismiss()
active_screens[idx] = nil
else
active_screens[idx] = true
end
pending = pending - 1 pending = pending - 1
if pending == 0 then if pending == 0 then
df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT 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 df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT
self:restore_placement() self:restore_placement()
end, 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 end
else else
self:place_building(get_placement_data()) self:place_building(get_placement_data())