From 090f298a47a3ea54b39ba4f51a1d57368b5faaca Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 2 Mar 2023 06:24:19 -0800 Subject: [PATCH 01/16] init building materials to ordinary max quality --- plugins/buildingplan/defaultitemfilters.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/plugins/buildingplan/defaultitemfilters.cpp b/plugins/buildingplan/defaultitemfilters.cpp index 36d074363..851565370 100644 --- a/plugins/buildingplan/defaultitemfilters.cpp +++ b/plugins/buildingplan/defaultitemfilters.cpp @@ -20,6 +20,17 @@ BuildingTypeKey DefaultItemFilters::getKey(PersistentDataItem &filter_config) { get_config_val(filter_config, FILTER_CONFIG_CUSTOM)); } +static int get_max_quality(const df::job_item *jitem) { + if (jitem->flags2.bits.building_material || + jitem->item_type == df::item_type::WOOD || + jitem->item_type == df::item_type::BLOCKS || + jitem->item_type == df::item_type::BAR || + jitem->item_type == df::item_type::BOULDER) + return df::item_quality::Ordinary; + + return df::item_quality::Masterful; +} + DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector &jitems) : key(key) { DEBUG(status,out).print("creating persistent data for filter key %d,%d,%d\n", @@ -29,6 +40,9 @@ DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey 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)); item_filters.resize(jitems.size()); + for (size_t idx = 0; idx < jitems.size(); ++idx) { + item_filters[idx].setMaxQuality(get_max_quality(jitems[idx])); + } filter_config.val() = serialize_item_filters(item_filters); } From 3b116c80616e808a3a176e58870261bf0e4275f8 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 2 Mar 2023 06:31:54 -0800 Subject: [PATCH 02/16] give global settings page an interior frame --- plugins/lua/buildingplan.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index a0da8d838..d472a1e28 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -952,7 +952,7 @@ GlobalSettingsPage = defclass(GlobalSettingsPage, widgets.ResizingPanel) GlobalSettingsPage.ATTRS{ autoarrange_subviews=true, frame={t=0, l=0}, - frame_inset={l=1, r=1}, + frame_style=gui.INTERIOR_FRAME, } function GlobalSettingsPage:init() From d3ef96cc052bd83c33cee5d8344bd6a528b9da71 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 2 Mar 2023 17:49:28 -0800 Subject: [PATCH 03/16] allow MaterialInfo structs to be sorted --- library/include/modules/Materials.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 74acf5a8d..4018fcb2a 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -169,6 +169,9 @@ namespace DFHack inline bool operator!= (const MaterialInfo &a, const MaterialInfo &b) { return a.type != b.type || a.index != b.index; } + inline bool operator< (const MaterialInfo &a, const MaterialInfo &b) { + return a.type < b.type || (a.type == b.type && a.index < b.index); + } DFHACK_EXPORT bool isSoilInorganic(int material); DFHACK_EXPORT bool isStoneInorganic(int material); From f9924d90903969e0672ef80134fc0cb72bedbd25 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 2 Mar 2023 17:50:12 -0800 Subject: [PATCH 04/16] implement material filter setting and retrieving --- plugins/buildingplan/buildingplan.cpp | 174 ++++++++++++++++++++--- plugins/buildingplan/itemfilter.cpp | 20 +-- plugins/buildingplan/itemfilter.h | 6 +- plugins/buildingplan/plannedbuilding.cpp | 8 +- 4 files changed, 173 insertions(+), 35 deletions(-) diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 7479d348d..55e8f0e64 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -14,6 +14,7 @@ #include "df/world.h" using std::map; +using std::set; using std::string; using std::unordered_map; using std::vector; @@ -52,7 +53,7 @@ void set_config_bool(PersistentDataItem &c, int index, bool value) { static PersistentDataItem config; // for use in counting available materials for the UI -static vector mat_cache; +static map> mat_cache; static unordered_map, BuildingTypeKeyHash> job_item_cache; static unordered_map cur_heat_safety; static unordered_map cur_item_filters; @@ -144,19 +145,25 @@ static const vector & get_job_items(color_ostream &out, Bu } static void cache_matched(int16_t type, int32_t index) { - static const df::dfhack_material_category building_material_categories( - df::dfhack_material_category::mask_glass | - df::dfhack_material_category::mask_metal | - df::dfhack_material_category::mask_soap | - df::dfhack_material_category::mask_stone | - df::dfhack_material_category::mask_wood - ); + static const df::dfhack_material_category stone_cat(df::dfhack_material_category::mask_stone); + static const df::dfhack_material_category wood_cat(df::dfhack_material_category::mask_wood); + static const df::dfhack_material_category metal_cat(df::dfhack_material_category::mask_metal); + static const df::dfhack_material_category other_cat(df::dfhack_material_category::mask_glass | df::dfhack_material_category::mask_soap); MaterialInfo mi; mi.decode(type, index); - if (mi.matches(building_material_categories)) { - DEBUG(status).print("cached material: %s\n", mi.toString().c_str()); - mat_cache.emplace_back(mi); + if (mi.matches(stone_cat)) { + DEBUG(status).print("cached stone material: %s\n", mi.toString().c_str()); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "stone")); + } else if (mi.matches(wood_cat)) { + DEBUG(status).print("cached wood material: %s\n", mi.toString().c_str()); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "wood")); + } else if (mi.matches(metal_cat)) { + DEBUG(status).print("cached metal material: %s\n", mi.toString().c_str()); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "metal")); + } else if (mi.matches(other_cat)) { + DEBUG(status).print("cached other material: %s\n", mi.toString().c_str()); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "other")); } else TRACE(status).print("not matched: %s\n", mi.toString().c_str()); @@ -601,7 +608,8 @@ static void scheduleCycle(color_ostream &out) { } static int scanAvailableItems(color_ostream &out, df::building_type type, int16_t subtype, - int32_t custom, int index, vector *item_ids = NULL) { + int32_t custom, int index, vector *item_ids = NULL, + map *counts = NULL) { DEBUG(status,out).print( "entering countAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", type, subtype, custom, index); @@ -619,9 +627,20 @@ static int scanAvailableItems(color_ostream &out, df::building_type type, int16_ for (auto vector_id : vector_ids) { auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); for (auto &item : df::global::world->items.other[other_id]) { - if (itemPassesScreen(item) && matchesFilters(item, jitem, heat, item_filters[index])) { + ItemFilter filter = item_filters[index]; + if (counts) { + // don't filter by material; we want counts for all materials + filter.setMaterialMask(0); + filter.setMaterials(set()); + } + if (itemPassesScreen(item) && matchesFilters(item, jitem, heat, filter)) { if (item_ids) item_ids->emplace_back(item->id); + if (counts) { + MaterialInfo mi; + mi.decode(item); + (*counts)[mi]++; + } ++count; } } @@ -690,9 +709,103 @@ static void clearFilter(color_ostream &out, df::building_type type, int16_t subt call_buildingplan_lua(&out, "signal_reset"); } -static void setMaterialFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index, string filter) { - DEBUG(status,out).print("entering setMaterialFilter\n"); - call_buildingplan_lua(&out, "signal_reset"); +static int setMaterialMaskFilter(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + df::building_type type = (df::building_type)luaL_checkint(L, 1); + int16_t subtype = luaL_checkint(L, 2); + int32_t custom = luaL_checkint(L, 3); + int index = luaL_checkint(L, 4); + DEBUG(status,*out).print( + "entering setMaterialMaskFilter building_type=%d subtype=%d custom=%d index=%d\n", + type, subtype, custom, index); + BuildingTypeKey key(type, subtype, custom); + auto &filters = get_item_filters(*out, key).getItemFilters(); + if (index < 0 || filters.size() <= (size_t)index) + return 0; + uint32_t mask = 0; + vector cats; + Lua::GetVector(L, cats); + for (auto &cat : cats) { + if (cat == "stone") + mask |= df::dfhack_material_category::mask_stone; + else if (cat == "wood") + mask |= df::dfhack_material_category::mask_wood; + else if (cat == "metal") + mask |= df::dfhack_material_category::mask_metal; + else if (cat == "other") + mask |= df::dfhack_material_category::mask_glass | df::dfhack_material_category::mask_soap; + } + DEBUG(status,*out).print( + "setting material mask filter for building_type=%d subtype=%d custom=%d index=%d to %x\n", + type, subtype, custom, index, mask); + ItemFilter filter = filters[index]; + filter.setMaterialMask(mask); + get_item_filters(*out, key).setItemFilter(*out, filter, index); + call_buildingplan_lua(out, "signal_reset"); + return 0; +} + +static int getMaterialMaskFilter(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + df::building_type type = (df::building_type)luaL_checkint(L, 1); + int16_t subtype = luaL_checkint(L, 2); + int32_t custom = luaL_checkint(L, 3); + int index = luaL_checkint(L, 4); + DEBUG(status,*out).print( + "entering getMaterialFilter building_type=%d subtype=%d custom=%d index=%d\n", + type, subtype, custom, index); + BuildingTypeKey key(type, subtype, custom); + auto &filters = get_item_filters(*out, key); + if (index < 0 || filters.getItemFilters().size() <= (size_t)index) + return 0; + vector cat_names; + uint32_t bits = filters.getItemFilters()[index].getMaterialMask().whole; + if (!bits || bits & df::dfhack_material_category::mask_stone) + cat_names.emplace_back("stone"); + if (!bits || bits & df::dfhack_material_category::mask_wood) + cat_names.emplace_back("wood"); + if (!bits || bits & df::dfhack_material_category::mask_metal) + cat_names.emplace_back("metal"); + if (!bits || bits & (df::dfhack_material_category::mask_glass | df::dfhack_material_category::mask_soap)) + cat_names.emplace_back("other"); + Lua::PushVector(L, cat_names); + return 1; +} + +static int setMaterialFilter(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + df::building_type type = (df::building_type)luaL_checkint(L, 1); + int16_t subtype = luaL_checkint(L, 2); + int32_t custom = luaL_checkint(L, 3); + int index = luaL_checkint(L, 4); + DEBUG(status,*out).print( + "entering setMaterialFilter building_type=%d subtype=%d custom=%d index=%d\n", + type, subtype, custom, index); + BuildingTypeKey key(type, subtype, custom); + auto &filters = get_item_filters(*out, key).getItemFilters(); + if (index < 0 || filters.size() <= (size_t)index) + return 0; + set mats; + vector matstrs; + Lua::GetVector(L, matstrs); + for (auto &mat : matstrs) { + if (mat_cache.count(mat)) + mats.emplace(mat_cache.at(mat).first); + } + DEBUG(status,*out).print( + "setting material filter for building_type=%d subtype=%d custom=%d index=%d to %zd materials\n", + type, subtype, custom, index, mats.size()); + ItemFilter filter = filters[index]; + filter.setMaterials(mats); + get_item_filters(*out, key).setItemFilter(*out, filter, index); + call_buildingplan_lua(out, "signal_reset"); + return 0; } static int getMaterialFilter(lua_State *L) { @@ -706,8 +819,29 @@ static int getMaterialFilter(lua_State *L) { DEBUG(status,*out).print( "entering getMaterialFilter building_type=%d subtype=%d custom=%d index=%d\n", type, subtype, custom, index); - map counts_per_material; - Lua::Push(L, counts_per_material); + BuildingTypeKey key(type, subtype, custom); + auto &filters = get_item_filters(*out, key).getItemFilters(); + if (index < 0 || filters.size() <= (size_t)index) + return 0; + const auto &mat_filter = filters[index].getMaterials(); + map counts; + scanAvailableItems(*out, type, subtype, custom, index, NULL, &counts); + // name -> {count=int, enabled=bool, category=string} + map> ret; + for (auto & entry : mat_cache) { + auto &name = entry.first; + auto &mat = entry.second.first; + auto &cat = entry.second.second; + map props; + string count = "0"; + if (counts.count(mat)) + count = int_to_string(counts.at(mat)); + props.emplace("count", count); + props.emplace("enabled", (!mat_filter.size() || mat_filter.count(mat)) ? "true" : "false"); + props.emplace("category", cat); + ret.emplace(name, props); + } + Lua::Push(L, ret); return 1; } @@ -874,7 +1008,6 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(countAvailableItems), DFHACK_LUA_FUNCTION(hasFilter), DFHACK_LUA_FUNCTION(clearFilter), - DFHACK_LUA_FUNCTION(setMaterialFilter), DFHACK_LUA_FUNCTION(setHeatSafetyFilter), DFHACK_LUA_FUNCTION(setQualityFilter), DFHACK_LUA_FUNCTION(getDescString), @@ -886,6 +1019,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getGlobalSettings), DFHACK_LUA_COMMAND(getAvailableItems), + DFHACK_LUA_COMMAND(setMaterialMaskFilter), + DFHACK_LUA_COMMAND(getMaterialMaskFilter), + DFHACK_LUA_COMMAND(setMaterialFilter), DFHACK_LUA_COMMAND(getMaterialFilter), DFHACK_LUA_COMMAND(getHeatSafetyFilter), DFHACK_LUA_COMMAND(getQualityFilter), diff --git a/plugins/buildingplan/itemfilter.cpp b/plugins/buildingplan/itemfilter.cpp index 86c9c1378..c7794e109 100644 --- a/plugins/buildingplan/itemfilter.cpp +++ b/plugins/buildingplan/itemfilter.cpp @@ -8,6 +8,7 @@ namespace DFHack { DBG_EXTERN(buildingplan, status); } +using std::set; using std::string; using std::vector; @@ -44,7 +45,7 @@ static bool deserializeMaterialMask(string ser, df::dfhack_material_category mat return true; } -static bool deserializeMaterials(string ser, vector &materials) { +static bool deserializeMaterials(string ser, set &materials) { if (ser.empty()) return true; @@ -56,7 +57,7 @@ static bool deserializeMaterials(string ser, vector &mater DEBUG(status).print("invalid material name serialization: '%s'", ser.c_str()); return false; } - materials.push_back(material); + materials.emplace(material); } return true; } @@ -83,10 +84,11 @@ ItemFilter::ItemFilter(color_ostream &out, string serialized) { string ItemFilter::serialize() const { std::ostringstream ser; ser << bitfield_to_string(mat_mask, ",") << "/"; + vector matstrs; if (!materials.empty()) { - ser << materials[0].getToken(); - for (size_t i = 1; i < materials.size(); ++i) - ser << "," << materials[i].getToken(); + for (auto &mat : materials) + matstrs.emplace_back(mat.getToken()); + ser << join_strings(",", matstrs); } ser << "/" << static_cast(min_quality); ser << "/" << static_cast(max_quality); @@ -127,7 +129,7 @@ void ItemFilter::setMaterialMask(uint32_t mask) { mat_mask.whole = mask; } -void ItemFilter::setMaterials(const vector &materials) { +void ItemFilter::setMaterials(const set &materials) { this->materials = materials; } @@ -140,8 +142,8 @@ bool ItemFilter::matches(df::dfhack_material_category mask) const { } bool ItemFilter::matches(DFHack::MaterialInfo &material) const { - for (auto it = materials.begin(); it != materials.end(); ++it) - if (material.matches(*it)) + for (auto &mat : materials) + if (material.matches(mat)) return true; return false; } @@ -161,7 +163,7 @@ bool ItemFilter::matches(df::item *item) const { } vector deserialize_item_filters(color_ostream &out, const string &serialized) { - std::vector filters; + vector filters; vector filter_strs; split_string(&filter_strs, serialized, ";"); diff --git a/plugins/buildingplan/itemfilter.h b/plugins/buildingplan/itemfilter.h index 29eb7226c..1bed7b7f6 100644 --- a/plugins/buildingplan/itemfilter.h +++ b/plugins/buildingplan/itemfilter.h @@ -18,13 +18,13 @@ public: void setMaxQuality(int quality); void setDecoratedOnly(bool decorated); void setMaterialMask(uint32_t mask); - void setMaterials(const std::vector &materials); + void setMaterials(const std::set &materials); df::item_quality getMinQuality() const { return min_quality; } df::item_quality getMaxQuality() const {return max_quality; } bool getDecoratedOnly() const { return decorated_only; } df::dfhack_material_category getMaterialMask() const { return mat_mask; } - std::vector getMaterials() const { return materials; } + std::set getMaterials() const { return materials; } bool matches(df::dfhack_material_category mask) const; bool matches(DFHack::MaterialInfo &material) const; @@ -35,7 +35,7 @@ private: df::item_quality max_quality; bool decorated_only; df::dfhack_material_category mat_mask; - std::vector materials; + std::set materials; }; std::vector deserialize_item_filters(DFHack::color_ostream &out, const std::string &serialized); diff --git a/plugins/buildingplan/plannedbuilding.cpp b/plugins/buildingplan/plannedbuilding.cpp index 27be36a5b..a693f6fcf 100644 --- a/plugins/buildingplan/plannedbuilding.cpp +++ b/plugins/buildingplan/plannedbuilding.cpp @@ -12,8 +12,10 @@ namespace DFHack { DBG_EXTERN(buildingplan, status); } +using std::set; using std::string; using std::vector; + using namespace DFHack; static vector> get_vector_ids(color_ostream &out, int bld_id) { @@ -58,13 +60,11 @@ static vector> deserialize_vector_ids(color_ostre return ret; } -static std::vector get_item_filters(color_ostream &out, PersistentDataItem &bld_config) { - std::vector ret; - +static vector get_item_filters(color_ostream &out, PersistentDataItem &bld_config) { vector rawstrs; split_string(&rawstrs, bld_config.val(), "|"); if (rawstrs.size() < 2) - return ret; + return vector(); return deserialize_item_filters(out, rawstrs[1]); } From 028bbca07b813837b34db3799a20b206c8b0291e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 2 Mar 2023 19:32:10 -0800 Subject: [PATCH 05/16] allow vectors to be read from indices other than 1 --- library/LuaTools.cpp | 4 ++-- library/include/LuaTools.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index ed0b0699d..a1bf855d6 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -121,10 +121,10 @@ void DFHack::Lua::Push(lua_State *state, const df::coord2d &pos) lua_setfield(state, -2, "y"); } -void DFHack::Lua::GetVector(lua_State *state, std::vector &pvec) +void DFHack::Lua::GetVector(lua_State *state, std::vector &pvec, int idx) { lua_pushnil(state); // first key - while (lua_next(state, 1) != 0) + while (lua_next(state, idx) != 0) { pvec.push_back(lua_tostring(state, -1)); lua_pop(state, 1); // remove value, leave key diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 86d3d0c7e..689d82260 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -359,7 +359,7 @@ namespace DFHack {namespace Lua { } } - DFHACK_EXPORT void GetVector(lua_State *state, std::vector &pvec); + DFHACK_EXPORT void GetVector(lua_State *state, std::vector &pvec, int idx = 1); DFHACK_EXPORT int PushPosXYZ(lua_State *state, const df::coord &pos); DFHACK_EXPORT int PushPosXY(lua_State *state, const df::coord2d &pos); From d07864e5bb83568cadbd3915bdc301c29cb1629b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 2 Mar 2023 19:32:25 -0800 Subject: [PATCH 06/16] allow material categories to be set from the ui --- plugins/buildingplan/buildingplan.cpp | 2 +- plugins/lua/buildingplan.lua | 92 +++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 55e8f0e64..aa23ddd0b 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -726,7 +726,7 @@ static int setMaterialMaskFilter(lua_State *L) { return 0; uint32_t mask = 0; vector cats; - Lua::GetVector(L, cats); + Lua::GetVector(L, cats, 5); for (auto &cat : cats) { if (cat == "stone") mask |= df::dfhack_material_category::mask_stone; diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index d472a1e28..5137ee700 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -639,6 +639,15 @@ local function can_be_improved(idx) filter.item_type ~= df.item_type.BOULDER end +local function mat_sort_by_name(a, b) + return a.name < b.name +end + +local function mat_sort_by_quantity(a, b) + return a.quantity > b.quantity or + (a.quantity == b.quantity and mat_sort_by_name(a, b)) +end + function QualityAndMaterialsPage:init() self.lowest_other_item_heat_safety = 2 self.dirty = true @@ -688,16 +697,23 @@ function QualityAndMaterialsPage:init() on_char=function(ch) return ch:match('%l') end, }, widgets.CycleHotkeyLabel{ + view_id='mat_sort', frame={l=24, t=4, w=21}, label='Sort by:', key='CUSTOM_SHIFT_R', - options={'name', 'available'}, + options={ + {label='name', value=mat_sort_by_name}, + {label='available', value=mat_sort_by_quantity} + }, + on_change=function() self.dirty = true end, }, widgets.ToggleHotkeyLabel{ + view_id='hide_zero', frame={l=24, t=5, w=24}, label='Hide unavailable:', key='CUSTOM_SHIFT_H', initial_option=false, + on_change=function() self.dirty = true end, }, widgets.Label{ frame={l=1, b=0}, @@ -720,20 +736,14 @@ function QualityAndMaterialsPage:init() view_id='materials_categories', frame={l=1, t=0, b=0, w=TYPE_COL_WIDTH-3}, scroll_keys={}, - choices={ - {text='Stone', key='CUSTOM_SHIFT_S'}, - {text='Wood', key='CUSTOM_SHIFT_O'}, - {text='Metal', key='CUSTOM_SHIFT_M'}, - {text='Other', key='CUSTOM_SHIFT_T'}, - }, + icon_width=2, + cursor_pen=COLOR_CYAN, + on_double_click=self:callback('toggle_category'), }, widgets.List{ view_id='materials_mats', frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0}, - choices={ - {text='9 - granite'}, - {text='0 - graphite'}, - }, + icon_width=2, }, }, }, @@ -853,6 +863,32 @@ function QualityAndMaterialsPage:init() } end +local MAT_ENABLED_PEN = to_pen{ch=string.char(251), fg=COLOR_LIGHTGREEN} +local MAT_DISABLED_PEN = to_pen{ch='x', fg=COLOR_RED} + +local function make_cat_choice(label, cat, key, enabled_cats) + local enabled = enabled_cats[cat] + return { + text=label, + key=key, + enabled=enabled, + cat=cat, + icon=enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN + } +end + +local function make_mat_choice(name, props) + local quantity = tonumber(props.count) + local text = ('%5d - %s'):format(quantity, name) + local enabled = props.enabled == 'true' + return { + text=text, + icon=enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN, + name=name, + quantity=quantity, + } +end + function QualityAndMaterialsPage:refresh() local summary = '' local subviews = self.subviews @@ -868,6 +904,26 @@ function QualityAndMaterialsPage:refresh() subviews.min_quality:setOption(quality.min_quality) subviews.max_quality:setOption(quality.max_quality) + local categories = utils.invert(getMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1)) + local category_choices={ + make_cat_choice('Stone', 'stone', 'CUSTOM_SHIFT_S', categories), + make_cat_choice('Wood', 'wood', 'CUSTOM_SHIFT_O', categories), + make_cat_choice('Metal', 'metal', 'CUSTOM_SHIFT_M', categories), + make_cat_choice('Other', 'other', 'CUSTOM_SHIFT_T', categories), + } + self.subviews.materials_categories:setChoices(category_choices) + + local mat_filter = getMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) + local mat_choices = {} + local hide_zero = self.subviews.hide_zero:getOptionValue() + for name,props in pairs(mat_filter) do + if not hide_zero or tonumber(props.count) > 0 then + table.insert(mat_choices, make_mat_choice(name, props)) + end + end + table.sort(mat_choices, self.subviews.mat_sort:getOptionValue()) + self.subviews.materials_mats:setChoices(mat_choices) + self.summary = summary self.dirty = false end @@ -877,6 +933,18 @@ function QualityAndMaterialsPage:get_summary() return self.summary end +function QualityAndMaterialsPage:toggle_category(idx, choice) + choice.enabled = not choice.enabled + local cats = {} + for _,c in ipairs(self.subviews.materials_categories:getChoices()) do + if c.enabled then + table.insert(cats, c.cat) + end + end + setMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, cats) + self.dirty = true +end + function QualityAndMaterialsPage:set_heat_safety(heat) setHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, heat) self.dirty = true @@ -1022,7 +1090,7 @@ end FilterSelection = defclass(FilterSelection, widgets.Window) FilterSelection.ATTRS{ frame_title='Choose filters [MOCK -- NOT FUNCTIONAL]', - frame={w=53, h=53, l=30, t=8}, + frame={w=55, h=53, l=30, t=8}, frame_inset={t=1}, resizable=true, index=DEFAULT_NIL, From 0562dc52343ee22d6388932d5ffdcc995940a6d7 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 18:16:49 -0800 Subject: [PATCH 07/16] constify some MaterialInfo methods --- library/include/modules/Materials.h | 16 ++++++++-------- library/modules/Materials.cpp | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 4018fcb2a..3cdf146c9 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -139,25 +139,25 @@ namespace DFHack std::string getToken() const; std::string toString(uint16_t temp = 10015, bool named = true) const; - bool isAnyCloth(); + bool isAnyCloth() const; - void getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask); - void getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask); - void getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask); + void getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) const; + void getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask) const; + void getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask) const; df::craft_material_class getCraftClass(); - bool matches(const MaterialInfo &mat) + bool matches(const MaterialInfo &mat) const { if (!mat.isValid()) return true; return (type == mat.type) && (mat.index == -1 || index == mat.index); } - bool matches(const df::job_material_category &cat); - bool matches(const df::dfhack_material_category &cat); + bool matches(const df::job_material_category &cat) const; + bool matches(const df::dfhack_material_category &cat) const; bool matches(const df::job_item &item, - df::item_type itype = df::item_type::NONE); + df::item_type itype = df::item_type::NONE) const; }; DFHACK_EXPORT bool parseJobMaterialCategory(df::job_material_category *cat, const std::string &token); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index a6141f1d8..d73b59922 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -376,7 +376,7 @@ df::craft_material_class MaterialInfo::getCraftClass() return craft_material_class::None; } -bool MaterialInfo::isAnyCloth() +bool MaterialInfo::isAnyCloth() const { using namespace df::enums::material_flags; @@ -387,7 +387,7 @@ bool MaterialInfo::isAnyCloth() ); } -bool MaterialInfo::matches(const df::job_material_category &cat) +bool MaterialInfo::matches(const df::job_material_category &cat) const { if (!material) return false; @@ -416,7 +416,7 @@ bool MaterialInfo::matches(const df::job_material_category &cat) return false; } -bool MaterialInfo::matches(const df::dfhack_material_category &cat) +bool MaterialInfo::matches(const df::dfhack_material_category &cat) const { if (!material) return false; @@ -444,7 +444,7 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) #undef TEST -bool MaterialInfo::matches(const df::job_item &item, df::item_type itype) +bool MaterialInfo::matches(const df::job_item &item, df::item_type itype) const { if (!isValid()) return false; @@ -465,7 +465,7 @@ bool MaterialInfo::matches(const df::job_item &item, df::item_type itype) bits_match(item.flags3.whole, ok3.whole, mask3.whole); } -void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) +void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -500,7 +500,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &ma //04000000 - "milkable" - vtable[107],1,1 } -void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask) +void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; @@ -538,7 +538,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(yarn, MAT_FLAG(YARN)); } -void MaterialInfo::getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask) +void MaterialInfo::getMatchBits(df::job_item_flags3 &ok, df::job_item_flags3 &mask) const { ok.whole = mask.whole = 0; if (!isValid()) return; From 2a2141931f59fcaeea12cb5fb4f9a5af3a8b12fd Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 18:17:10 -0800 Subject: [PATCH 08/16] allow material filter to be read and set --- plugins/buildingplan/buildingplan.cpp | 69 ++++++++++------ plugins/lua/buildingplan.lua | 114 +++++++++++++------------- 2 files changed, 103 insertions(+), 80 deletions(-) diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index aa23ddd0b..de133e668 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -144,12 +144,12 @@ static const vector & get_job_items(color_ostream &out, Bu return jitems; } -static void cache_matched(int16_t type, int32_t index) { - static const df::dfhack_material_category stone_cat(df::dfhack_material_category::mask_stone); - static const df::dfhack_material_category wood_cat(df::dfhack_material_category::mask_wood); - static const df::dfhack_material_category metal_cat(df::dfhack_material_category::mask_metal); - static const df::dfhack_material_category other_cat(df::dfhack_material_category::mask_glass | df::dfhack_material_category::mask_soap); +static const df::dfhack_material_category stone_cat(df::dfhack_material_category::mask_stone); +static const df::dfhack_material_category wood_cat(df::dfhack_material_category::mask_wood); +static const df::dfhack_material_category metal_cat(df::dfhack_material_category::mask_metal); +static const df::dfhack_material_category glass_cat(df::dfhack_material_category::mask_glass); +static void cache_matched(int16_t type, int32_t index) { MaterialInfo mi; mi.decode(type, index); if (mi.matches(stone_cat)) { @@ -161,9 +161,9 @@ static void cache_matched(int16_t type, int32_t index) { } else if (mi.matches(metal_cat)) { DEBUG(status).print("cached metal material: %s\n", mi.toString().c_str()); mat_cache.emplace(mi.toString(), std::make_pair(mi, "metal")); - } else if (mi.matches(other_cat)) { - DEBUG(status).print("cached other material: %s\n", mi.toString().c_str()); - mat_cache.emplace(mi.toString(), std::make_pair(mi, "other")); + } else if (mi.matches(glass_cat)) { + DEBUG(status).print("cached glass material: %s\n", mi.toString().c_str()); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "glass")); } else TRACE(status).print("not matched: %s\n", mi.toString().c_str()); @@ -729,19 +729,30 @@ static int setMaterialMaskFilter(lua_State *L) { Lua::GetVector(L, cats, 5); for (auto &cat : cats) { if (cat == "stone") - mask |= df::dfhack_material_category::mask_stone; + mask |= stone_cat.whole; else if (cat == "wood") - mask |= df::dfhack_material_category::mask_wood; + mask |= wood_cat.whole; else if (cat == "metal") - mask |= df::dfhack_material_category::mask_metal; - else if (cat == "other") - mask |= df::dfhack_material_category::mask_glass | df::dfhack_material_category::mask_soap; + mask |= metal_cat.whole; + else if (cat == "glass") + mask |= glass_cat.whole; } DEBUG(status,*out).print( "setting material mask filter for building_type=%d subtype=%d custom=%d index=%d to %x\n", type, subtype, custom, index, mask); ItemFilter filter = filters[index]; filter.setMaterialMask(mask); + if (mask) { + // remove materials from the list that don't match the mask + const auto &mats = filter.getMaterials(); + set new_mats; + const df::dfhack_material_category mat_mask(mask); + for (auto & mat : mats) { + if (mat.matches(mat_mask)) + new_mats.emplace(mat); + } + filter.setMaterials(new_mats); + } get_item_filters(*out, key).setItemFilter(*out, filter, index); call_buildingplan_lua(out, "signal_reset"); return 0; @@ -762,17 +773,14 @@ static int getMaterialMaskFilter(lua_State *L) { auto &filters = get_item_filters(*out, key); if (index < 0 || filters.getItemFilters().size() <= (size_t)index) return 0; - vector cat_names; + map ret; uint32_t bits = filters.getItemFilters()[index].getMaterialMask().whole; - if (!bits || bits & df::dfhack_material_category::mask_stone) - cat_names.emplace_back("stone"); - if (!bits || bits & df::dfhack_material_category::mask_wood) - cat_names.emplace_back("wood"); - if (!bits || bits & df::dfhack_material_category::mask_metal) - cat_names.emplace_back("metal"); - if (!bits || bits & (df::dfhack_material_category::mask_glass | df::dfhack_material_category::mask_soap)) - cat_names.emplace_back("other"); - Lua::PushVector(L, cat_names); + ret.emplace("unset", !bits); + ret.emplace("stone", !bits || bits & stone_cat.whole); + ret.emplace("wood", !bits || bits & wood_cat.whole); + ret.emplace("metal", !bits || bits & metal_cat.whole); + ret.emplace("glass", !bits || bits & glass_cat.whole); + Lua::Push(L, ret); return 1; } @@ -793,7 +801,7 @@ static int setMaterialFilter(lua_State *L) { return 0; set mats; vector matstrs; - Lua::GetVector(L, matstrs); + Lua::GetVector(L, matstrs, 5); for (auto &mat : matstrs) { if (mat_cache.count(mat)) mats.emplace(mat_cache.at(mat).first); @@ -803,6 +811,19 @@ static int setMaterialFilter(lua_State *L) { type, subtype, custom, index, mats.size()); ItemFilter filter = filters[index]; filter.setMaterials(mats); + // ensure relevant masks are explicitly enabled + df::dfhack_material_category mask = filter.getMaterialMask(); + for (auto & mat : mats) { + if (mat.matches(stone_cat)) + mask.whole |= stone_cat.whole; + else if (mat.matches(wood_cat)) + mask.whole |= wood_cat.whole; + else if (mat.matches(metal_cat)) + mask.whole |= metal_cat.whole; + else if (mat.matches(glass_cat)) + mask.whole |= glass_cat.whole; + } + filter.setMaterialMask(mask.whole); get_item_filters(*out, key).setItemFilter(*out, filter, index); call_buildingplan_lua(out, "signal_reset"); return 0; diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 5137ee700..fae27c3e5 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -622,7 +622,7 @@ QualityAndMaterialsPage.ATTRS{ } local TYPE_COL_WIDTH = 20 -local HEADER_HEIGHT = 8 +local HEADER_HEIGHT = 6 local QUALITY_HEIGHT = 9 local FOOTER_HEIGHT = 4 @@ -649,10 +649,9 @@ local function mat_sort_by_quantity(a, b) end function QualityAndMaterialsPage:init() - self.lowest_other_item_heat_safety = 2 self.dirty = true - local enable_item_quality = can_be_improved(self.index) + local enable_item_quality = can_be_improved(self.index) self:addviews{ widgets.Panel{ @@ -667,38 +666,9 @@ function QualityAndMaterialsPage:init() {gap=1, pen=COLOR_LIGHTCYAN, text=self:callback('get_summary')} }, }, - widgets.CycleHotkeyLabel{ - view_id='safety', - frame={t=2, l=0, w=35}, - key='CUSTOM_SHIFT_G', - label='Building heat safety:', - options={ - {label='Fire Magma', value=0, pen=COLOR_GREY}, - {label='Fire Magma', value=2, pen=COLOR_RED}, - {label='Fire', value=1, pen=COLOR_LIGHTRED}, - }, - on_change=self:callback('set_heat_safety'), - }, - widgets.Label{ - frame={t=2, l=30}, - text='Magma', - auto_width=true, - text_pen=COLOR_GREY, - visible=function() return self.subviews.safety:getOptionValue() == 1 end, - }, - widgets.Label{ - frame={t=3, l=3}, - text='Other items for this building may not be able to use all of their selected materials.', - visible=function() return self.subviews.safety:getOptionValue() > self.lowest_other_item_heat_safety end, - }, - widgets.EditField{ - frame={l=0, t=4, w=23}, - label_text='Search: ', - on_char=function(ch) return ch:match('%l') end, - }, widgets.CycleHotkeyLabel{ view_id='mat_sort', - frame={l=24, t=4, w=21}, + frame={l=0, t=2, w=21}, label='Sort by:', key='CUSTOM_SHIFT_R', options={ @@ -709,12 +679,17 @@ function QualityAndMaterialsPage:init() }, widgets.ToggleHotkeyLabel{ view_id='hide_zero', - frame={l=24, t=5, w=24}, + frame={l=0, t=3, w=24}, label='Hide unavailable:', key='CUSTOM_SHIFT_H', initial_option=false, on_change=function() self.dirty = true end, }, + widgets.EditField{ + frame={l=26, t=2}, + label_text='Search: ', + on_char=function(ch) return ch:match('[%l -]') end, + }, widgets.Label{ frame={l=1, b=0}, text='Type', @@ -727,8 +702,7 @@ function QualityAndMaterialsPage:init() }, }, }, - widgets.Panel{ - view_id='materials_lists', + widgets.Panel{view_id='materials_lists', frame={l=0, t=HEADER_HEIGHT, r=0, b=FOOTER_HEIGHT+QUALITY_HEIGHT}, frame_style=gui.INTERIOR_FRAME, subviews={ @@ -739,11 +713,13 @@ function QualityAndMaterialsPage:init() icon_width=2, cursor_pen=COLOR_CYAN, on_double_click=self:callback('toggle_category'), + on_submit=self:callback('toggle_category'), }, widgets.List{ view_id='materials_mats', frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0}, icon_width=2, + on_submit=self:callback('toggle_material'), }, }, }, @@ -866,25 +842,35 @@ end local MAT_ENABLED_PEN = to_pen{ch=string.char(251), fg=COLOR_LIGHTGREEN} local MAT_DISABLED_PEN = to_pen{ch='x', fg=COLOR_RED} -local function make_cat_choice(label, cat, key, enabled_cats) - local enabled = enabled_cats[cat] +local function make_cat_choice(label, cat, key, cats) + local enabled = cats[cat] + local icon = nil + if not cats.unset then + icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN + end return { text=label, key=key, enabled=enabled, cat=cat, - icon=enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN + icon=icon, } end -local function make_mat_choice(name, props) +local function make_mat_choice(name, props, cats) local quantity = tonumber(props.count) local text = ('%5d - %s'):format(quantity, name) - local enabled = props.enabled == 'true' + local enabled = props.enabled == 'true' and cats[props.category] + local icon = nil + if not cats.unset then + icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN + end return { text=text, - icon=enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN, + enabled=enabled, + icon=icon, name=name, + cat=props.category, quantity=quantity, } end @@ -894,7 +880,6 @@ function QualityAndMaterialsPage:refresh() local subviews = self.subviews local heat = getHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type) - subviews.safety:setOption(heat) if heat >= 2 then summary = summary .. 'Magma safe ' elseif heat == 1 then summary = summary .. 'Fire safe ' end @@ -904,12 +889,12 @@ function QualityAndMaterialsPage:refresh() subviews.min_quality:setOption(quality.min_quality) subviews.max_quality:setOption(quality.max_quality) - local categories = utils.invert(getMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1)) + local cats = getMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) local category_choices={ - make_cat_choice('Stone', 'stone', 'CUSTOM_SHIFT_S', categories), - make_cat_choice('Wood', 'wood', 'CUSTOM_SHIFT_O', categories), - make_cat_choice('Metal', 'metal', 'CUSTOM_SHIFT_M', categories), - make_cat_choice('Other', 'other', 'CUSTOM_SHIFT_T', categories), + make_cat_choice('Stone', 'stone', 'CUSTOM_SHIFT_S', cats), + make_cat_choice('Wood', 'wood', 'CUSTOM_SHIFT_O', cats), + make_cat_choice('Metal', 'metal', 'CUSTOM_SHIFT_M', cats), + make_cat_choice('Glass', 'glass', 'CUSTOM_SHIFT_G', cats), } self.subviews.materials_categories:setChoices(category_choices) @@ -918,7 +903,7 @@ function QualityAndMaterialsPage:refresh() local hide_zero = self.subviews.hide_zero:getOptionValue() for name,props in pairs(mat_filter) do if not hide_zero or tonumber(props.count) > 0 then - table.insert(mat_choices, make_mat_choice(name, props)) + table.insert(mat_choices, make_mat_choice(name, props, cats)) end end table.sort(mat_choices, self.subviews.mat_sort:getOptionValue()) @@ -933,20 +918,37 @@ function QualityAndMaterialsPage:get_summary() return self.summary end -function QualityAndMaterialsPage:toggle_category(idx, choice) - choice.enabled = not choice.enabled +function QualityAndMaterialsPage:toggle_category(_, choice) local cats = {} - for _,c in ipairs(self.subviews.materials_categories:getChoices()) do - if c.enabled then - table.insert(cats, c.cat) + if not choice.icon then + -- toggling from unset to something is set + table.insert(cats, choice.cat) + else + choice.enabled = not choice.enabled + for _,c in ipairs(self.subviews.materials_categories:getChoices()) do + if c.enabled then + table.insert(cats, c.cat) + end end end setMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, cats) self.dirty = true end -function QualityAndMaterialsPage:set_heat_safety(heat) - setHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, heat) +function QualityAndMaterialsPage:toggle_material(_, choice) + local mats = {} + if not choice.icon then + -- toggling from unset to something is set + table.insert(mats, choice.name) + else + choice.enabled = not choice.enabled + for _,c in ipairs(self.subviews.materials_mats:getChoices()) do + if c.enabled then + table.insert(mats, c.name) + end + end + end + setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats) self.dirty = true end From c752223fbcb41e21f9ef832a97bc285d966dd84b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 18:31:03 -0800 Subject: [PATCH 09/16] implement invert and reset --- plugins/lua/buildingplan.lua | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index fae27c3e5..a6c98cf2e 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -712,7 +712,6 @@ function QualityAndMaterialsPage:init() scroll_keys={}, icon_width=2, cursor_pen=COLOR_CYAN, - on_double_click=self:callback('toggle_category'), on_submit=self:callback('toggle_category'), }, widgets.List{ @@ -818,21 +817,17 @@ function QualityAndMaterialsPage:init() }, widgets.HotkeyLabel{ frame={l=30, t=0}, - label='Select all', - auto_width=true, - key='CUSTOM_SHIFT_A', - }, - widgets.HotkeyLabel{ - frame={l=30, t=1}, label='Invert selection', auto_width=true, key='CUSTOM_SHIFT_I', + on_activate=self:callback('invert_materials'), }, widgets.HotkeyLabel{ frame={l=30, t=2}, - label='Clear selection', + label='Reset filter', auto_width=true, - key='CUSTOM_SHIFT_C', + key='CUSTOM_SHIFT_X', + on_activate=self:callback('clear_filter'), }, }, } @@ -952,6 +947,23 @@ function QualityAndMaterialsPage:toggle_material(_, choice) self.dirty = true end +function QualityAndMaterialsPage:invert_materials() + local mats = {} + for _,c in ipairs(self.subviews.materials_mats:getChoices()) do + if not c.icon then return end + if not c.enabled then + table.insert(mats, c.name) + end + end + setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats) + self.dirty = true +end + +function QualityAndMaterialsPage:clear_filter() + clearFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) + self.dirty = true +end + function QualityAndMaterialsPage:set_decorated(decorated) local subviews = self.subviews setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, From ea549f6572d79eeff89576147931db23e2c813f9 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 19:15:04 -0800 Subject: [PATCH 10/16] generate text summary as the filter changes --- plugins/buildingplan/buildingplan.cpp | 6 +- plugins/lua/buildingplan.lua | 130 ++++++++++++++------------ 2 files changed, 74 insertions(+), 62 deletions(-) diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index de133e668..99e9a9af2 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -742,17 +742,17 @@ static int setMaterialMaskFilter(lua_State *L) { type, subtype, custom, index, mask); ItemFilter filter = filters[index]; filter.setMaterialMask(mask); + set new_mats; if (mask) { // remove materials from the list that don't match the mask const auto &mats = filter.getMaterials(); - set new_mats; const df::dfhack_material_category mat_mask(mask); for (auto & mat : mats) { if (mat.matches(mat_mask)) new_mats.emplace(mat); } - filter.setMaterials(new_mats); } + filter.setMaterials(new_mats); get_item_filters(*out, key).setItemFilter(*out, filter, index); call_buildingplan_lua(out, "signal_reset"); return 0; @@ -813,6 +813,8 @@ static int setMaterialFilter(lua_State *L) { filter.setMaterials(mats); // ensure relevant masks are explicitly enabled df::dfhack_material_category mask = filter.getMaterialMask(); + if (!mats.size()) + mask.whole = 0; // if all materials are disabled, reset the mask for (auto & mat : mats) { if (mat.matches(stone_cat)) mask.whole |= stone_cat.whole; diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index a6c98cf2e..898b520b8 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -96,6 +96,44 @@ local function get_quantity(filter, hollow, placement_data) return quantity * dimx * dimy * dimz end +local function to_title_case(str) + str = str:gsub('(%a)([%w_]*)', + function (first, rest) return first:upper()..rest:lower() end) + str = str:gsub('_', ' ') + return str +end + +function get_desc(filter) + local desc = 'Unknown' + if filter.has_tool_use and filter.has_tool_use > -1 then + desc = to_title_case(df.tool_uses[filter.has_tool_use]) + elseif filter.flags2 and filter.flags2.screw then + desc = 'Screw' + elseif filter.item_type and filter.item_type > -1 then + desc = to_title_case(df.item_type[filter.item_type]) + elseif filter.vector_id and filter.vector_id > -1 then + desc = to_title_case(df.job_item_vector_id[filter.vector_id]) + elseif filter.flags2 and filter.flags2.building_material then + desc = 'Building material'; + if filter.flags2.fire_safe then + desc = 'Fire-safe material'; + end + if filter.flags2.magma_safe then + desc = 'Magma-safe material'; + end + end + + if desc:endswith('s') then + desc = desc:sub(1,-2) + end + if desc == 'Trappart' then + desc = 'Mechanism' + elseif desc == 'Wood' then + desc = 'Log' + end + return desc +end + local BUTTON_START_PEN, BUTTON_END_PEN, SELECTED_ITEM_PEN = nil, nil, nil local reset_counts_flag = false local reset_inspector_flag = false @@ -622,7 +660,7 @@ QualityAndMaterialsPage.ATTRS{ } local TYPE_COL_WIDTH = 20 -local HEADER_HEIGHT = 6 +local HEADER_HEIGHT = 7 local QUALITY_HEIGHT = 9 local FOOTER_HEIGHT = 4 @@ -650,6 +688,7 @@ end function QualityAndMaterialsPage:init() self.dirty = true + self.summary = '' local enable_item_quality = can_be_improved(self.index) @@ -660,15 +699,18 @@ function QualityAndMaterialsPage:init() frame_inset={l=1}, subviews={ widgets.Label{ - frame={l=0, t=0, h=1, r=0}, - text={ - 'Current filter:', - {gap=1, pen=COLOR_LIGHTCYAN, text=self:callback('get_summary')} - }, + frame={l=0, t=0}, + text='Current filter:', + }, + widgets.WrappedLabel{ + frame={l=16, t=0, h=2, r=0}, + text_pen=COLOR_LIGHTCYAN, + text_to_wrap=function() return self.summary end, + auto_height=false, }, widgets.CycleHotkeyLabel{ view_id='mat_sort', - frame={l=0, t=2, w=21}, + frame={l=0, t=3, w=21}, label='Sort by:', key='CUSTOM_SHIFT_R', options={ @@ -679,14 +721,14 @@ function QualityAndMaterialsPage:init() }, widgets.ToggleHotkeyLabel{ view_id='hide_zero', - frame={l=0, t=3, w=24}, + frame={l=0, t=4, w=24}, label='Hide unavailable:', key='CUSTOM_SHIFT_H', initial_option=false, on_change=function() self.dirty = true end, }, widgets.EditField{ - frame={l=26, t=2}, + frame={l=26, t=3}, label_text='Search: ', on_char=function(ch) return ch:match('[%l -]') end, }, @@ -852,10 +894,9 @@ local function make_cat_choice(label, cat, key, cats) } end -local function make_mat_choice(name, props, cats) +local function make_mat_choice(name, props, enabled, cats) local quantity = tonumber(props.count) local text = ('%5d - %s'):format(quantity, name) - local enabled = props.enabled == 'true' and cats[props.category] local icon = nil if not cats.unset then icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN @@ -871,12 +912,13 @@ local function make_mat_choice(name, props, cats) end function QualityAndMaterialsPage:refresh() - local summary = '' + local summary = get_desc(get_cur_filters()[self.index]) local subviews = self.subviews local heat = getHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type) - if heat >= 2 then summary = summary .. 'Magma safe ' - elseif heat == 1 then summary = summary .. 'Fire safe ' + if heat >= 2 then summary = 'Magma safe ' .. summary + elseif heat == 1 then summary = 'Fire safe ' .. summary + else summary = 'Any ' .. summary end local quality = getQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) @@ -893,24 +935,30 @@ function QualityAndMaterialsPage:refresh() } self.subviews.materials_categories:setChoices(category_choices) - local mat_filter = getMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) + local mats = getMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) local mat_choices = {} local hide_zero = self.subviews.hide_zero:getOptionValue() - for name,props in pairs(mat_filter) do + local enabled_mat_names = {} + for name,props in pairs(mats) do + local enabled = props.enabled == 'true' and cats[props.category] + if not cats.unset and enabled then + table.insert(enabled_mat_names, name) + end if not hide_zero or tonumber(props.count) > 0 then - table.insert(mat_choices, make_mat_choice(name, props, cats)) + table.insert(mat_choices, make_mat_choice(name, props, enabled, cats)) end end table.sort(mat_choices, self.subviews.mat_sort:getOptionValue()) self.subviews.materials_mats:setChoices(mat_choices) + if #enabled_mat_names > 0 then + table.sort(enabled_mat_names) + summary = summary .. (' of %s'):format(table.concat(enabled_mat_names, ', ')) + end + self.summary = summary self.dirty = false -end - -function QualityAndMaterialsPage:get_summary() - -- TODO: summarize materials - return self.summary + self:updateLayout() end function QualityAndMaterialsPage:toggle_category(_, choice) @@ -1228,13 +1276,6 @@ local function is_over_options_panel() return v:getMousePos() end -local function to_title_case(str) - str = str:gsub('(%a)([%w_]*)', - function (first, rest) return first:upper()..rest:lower() end) - str = str:gsub('_', ' ') - return str -end - ItemLine = defclass(ItemLine, widgets.Panel) ItemLine.ATTRS{ idx=DEFAULT_NIL, @@ -1303,37 +1344,6 @@ function ItemLine:get_x_pen() COLOR_GREEN or COLOR_GREY end -function get_desc(filter) - local desc = 'Unknown' - if filter.has_tool_use and filter.has_tool_use > -1 then - desc = to_title_case(df.tool_uses[filter.has_tool_use]) - elseif filter.flags2 and filter.flags2.screw then - desc = 'Screw' - elseif filter.item_type and filter.item_type > -1 then - desc = to_title_case(df.item_type[filter.item_type]) - elseif filter.vector_id and filter.vector_id > -1 then - desc = to_title_case(df.job_item_vector_id[filter.vector_id]) - elseif filter.flags2 and filter.flags2.building_material then - desc = 'Building material'; - if filter.flags2.fire_safe then - desc = 'Fire-safe material'; - end - if filter.flags2.magma_safe then - desc = 'Magma-safe material'; - end - end - - if desc:endswith('s') then - desc = desc:sub(1,-2) - end - if desc == 'Trappart' then - desc = 'Mechanism' - elseif desc == 'Wood' then - desc = 'Log' - end - return desc -end - function ItemLine:get_item_line_text() local idx = self.idx local filter = get_cur_filters()[idx] From d009668339ac37cbac00d07b05855b473345147d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 22:09:21 -0800 Subject: [PATCH 11/16] implement materials search --- plugins/lua/buildingplan.lua | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 898b520b8..eae8a9b43 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -728,6 +728,7 @@ function QualityAndMaterialsPage:init() on_change=function() self.dirty = true end, }, widgets.EditField{ + view_id='search', frame={l=26, t=3}, label_text='Search: ', on_char=function(ch) return ch:match('[%l -]') end, @@ -744,7 +745,8 @@ function QualityAndMaterialsPage:init() }, }, }, - widgets.Panel{view_id='materials_lists', + widgets.Panel{ + view_id='materials_lists', frame={l=0, t=HEADER_HEIGHT, r=0, b=FOOTER_HEIGHT+QUALITY_HEIGHT}, frame_style=gui.INTERIOR_FRAME, subviews={ @@ -756,7 +758,7 @@ function QualityAndMaterialsPage:init() cursor_pen=COLOR_CYAN, on_submit=self:callback('toggle_category'), }, - widgets.List{ + widgets.FilteredList{ view_id='materials_mats', frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0}, icon_width=2, @@ -874,6 +876,12 @@ function QualityAndMaterialsPage:init() }, } } + + -- replace the FilteredList's built-in EditField with our own + self.subviews.materials_mats.list.frame.t = 0 + self.subviews.materials_mats.edit.visible = false + self.subviews.materials_mats.edit = self.subviews.search + self.subviews.search.on_change = self.subviews.materials_mats:callback('onFilterChange') end local MAT_ENABLED_PEN = to_pen{ch=string.char(251), fg=COLOR_LIGHTGREEN} @@ -949,7 +957,10 @@ function QualityAndMaterialsPage:refresh() end end table.sort(mat_choices, self.subviews.mat_sort:getOptionValue()) + + local prev_filter = self.subviews.search.text self.subviews.materials_mats:setChoices(mat_choices) + self.subviews.materials_mats:setFilter(prev_filter) if #enabled_mat_names > 0 then table.sort(enabled_mat_names) @@ -984,9 +995,12 @@ function QualityAndMaterialsPage:toggle_material(_, choice) -- toggling from unset to something is set table.insert(mats, choice.name) else - choice.enabled = not choice.enabled for _,c in ipairs(self.subviews.materials_mats:getChoices()) do - if c.enabled then + local enabled = c.enabled + if choice.name == c.name then + enabled = not c.enabled + end + if enabled then table.insert(mats, c.name) end end From efb307ed25cf8600d3c55e71bcf508f6e65f51cd Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 22:09:45 -0800 Subject: [PATCH 12/16] remove mock warning --- plugins/lua/buildingplan.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index eae8a9b43..ca193d38b 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -1165,7 +1165,7 @@ end FilterSelection = defclass(FilterSelection, widgets.Window) FilterSelection.ATTRS{ - frame_title='Choose filters [MOCK -- NOT FUNCTIONAL]', + frame_title='Choose filters', frame={w=55, h=53, l=30, t=8}, frame_inset={t=1}, resizable=true, From 80addc92d128ea93272c7d15b53e9e1c3c406195 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 23:04:03 -0800 Subject: [PATCH 13/16] remember default max quality for each item --- plugins/buildingplan/buildingplan.cpp | 4 +++- plugins/buildingplan/defaultitemfilters.cpp | 2 +- plugins/buildingplan/itemfilter.cpp | 20 ++++++++++++-------- plugins/buildingplan/itemfilter.h | 3 ++- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 99e9a9af2..ec85d7952 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -705,7 +705,9 @@ static void clearFilter(color_ostream &out, df::building_type type, int16_t subt auto &filters = get_item_filters(out, key); if (index < 0 || filters.getItemFilters().size() <= (size_t)index) return; - filters.setItemFilter(out, ItemFilter(), index); + ItemFilter filter = filters.getItemFilters()[index]; + filter.clear(); + filters.setItemFilter(out, filter, index); call_buildingplan_lua(&out, "signal_reset"); } diff --git a/plugins/buildingplan/defaultitemfilters.cpp b/plugins/buildingplan/defaultitemfilters.cpp index 851565370..3c3b2f3a9 100644 --- a/plugins/buildingplan/defaultitemfilters.cpp +++ b/plugins/buildingplan/defaultitemfilters.cpp @@ -41,7 +41,7 @@ DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, set_config_val(filter_config, FILTER_CONFIG_CUSTOM, std::get<2>(key)); item_filters.resize(jitems.size()); for (size_t idx = 0; idx < jitems.size(); ++idx) { - item_filters[idx].setMaxQuality(get_max_quality(jitems[idx])); + item_filters[idx].setMaxQuality(get_max_quality(jitems[idx]), true); } filter_config.val() = serialize_item_filters(item_filters); } diff --git a/plugins/buildingplan/itemfilter.cpp b/plugins/buildingplan/itemfilter.cpp index c7794e109..e9639f281 100644 --- a/plugins/buildingplan/itemfilter.cpp +++ b/plugins/buildingplan/itemfilter.cpp @@ -14,13 +14,13 @@ using std::vector; using namespace DFHack; -ItemFilter::ItemFilter() { +ItemFilter::ItemFilter() : default_max_quality(df::item_quality::Masterful) { clear(); } void ItemFilter::clear() { min_quality = df::item_quality::Ordinary; - max_quality = df::item_quality::Masterful; + max_quality = default_max_quality; decorated_only = false; mat_mask.whole = 0; materials.clear(); @@ -28,7 +28,7 @@ void ItemFilter::clear() { bool ItemFilter::isEmpty() const { return min_quality == df::item_quality::Ordinary - && max_quality == df::item_quality::Masterful + && max_quality == default_max_quality && !decorated_only && !mat_mask.whole && materials.empty(); @@ -62,12 +62,10 @@ static bool deserializeMaterials(string ser, set &material return true; } -ItemFilter::ItemFilter(color_ostream &out, string serialized) { - clear(); - +ItemFilter::ItemFilter(color_ostream &out, string serialized) : ItemFilter() { vector tokens; split_string(&tokens, serialized, "/"); - if (tokens.size() != 5) { + if (tokens.size() < 5) { DEBUG(status,out).print("invalid ItemFilter serialization: '%s'", serialized.c_str()); return; } @@ -78,6 +76,9 @@ ItemFilter::ItemFilter(color_ostream &out, string serialized) { setMinQuality(atoi(tokens[2].c_str())); setMaxQuality(atoi(tokens[3].c_str())); decorated_only = static_cast(atoi(tokens[4].c_str())); + + if (tokens.size() >= 6) + default_max_quality = static_cast(atoi(tokens[5].c_str())); } // format: mat,mask,elements/materials,list/minq/maxq/decorated @@ -93,6 +94,7 @@ string ItemFilter::serialize() const { ser << "/" << static_cast(min_quality); ser << "/" << static_cast(max_quality); ser << "/" << static_cast(decorated_only); + ser << "/" << static_cast(default_max_quality); return ser.str(); } @@ -114,11 +116,13 @@ void ItemFilter::setMinQuality(int quality) { max_quality = min_quality; } -void ItemFilter::setMaxQuality(int quality) { +void ItemFilter::setMaxQuality(int quality, bool is_default) { max_quality = static_cast(quality); clampItemQuality(&max_quality); if (max_quality < min_quality) min_quality = max_quality; + if (is_default) + default_max_quality = max_quality; } void ItemFilter::setDecoratedOnly(bool decorated) { diff --git a/plugins/buildingplan/itemfilter.h b/plugins/buildingplan/itemfilter.h index 1bed7b7f6..5ae59dd4a 100644 --- a/plugins/buildingplan/itemfilter.h +++ b/plugins/buildingplan/itemfilter.h @@ -15,7 +15,7 @@ public: std::string serialize() const; void setMinQuality(int quality); - void setMaxQuality(int quality); + void setMaxQuality(int quality, bool is_default = false); void setDecoratedOnly(bool decorated); void setMaterialMask(uint32_t mask); void setMaterials(const std::set &materials); @@ -33,6 +33,7 @@ public: private: df::item_quality min_quality; df::item_quality max_quality; + df::item_quality default_max_quality; bool decorated_only; df::dfhack_material_category mat_mask; std::set materials; From e9060624af9e892fa9a1bbe3d3bc6541b5a021ec Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 5 Mar 2023 23:04:35 -0800 Subject: [PATCH 14/16] use same hotkey for sorting, hide unused elements --- plugins/lua/buildingplan.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index ca193d38b..a666103e5 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -267,10 +267,11 @@ function ItemSelection:init() choices=self:get_choices(sort_by_recency), icon_width=2, on_submit=self:callback('toggle_group'), + edit_on_char=function(ch) return ch:match('[%l -]') end, }, widgets.CycleHotkeyLabel{ frame={l=0, b=2}, - key='CUSTOM_CTRL_X', + key='CUSTOM_SHIFT_R', label='Sort by:', options={ {label='Recently used', value=sort_by_recency}, @@ -288,7 +289,7 @@ function ItemSelection:init() }, widgets.HotkeyLabel{ frame={l=22, b=1}, - key='CUSTOM_CTRL_D', + key='CUSTOM_SHIFT_B', label='Build', auto_width=true, on_activate=self:callback('submit'), @@ -1760,7 +1761,7 @@ function PlannerOverlay:onInput(keys) end end end - return keys._MOUSE_L + return keys._MOUSE_L or keys.SELECT end function PlannerOverlay:render(dc) @@ -1994,6 +1995,7 @@ function InspectorOverlay:init() frame={t=11, l=0}, label='adjust filters', key='CUSTOM_CTRL_F', + visible=false, -- until implemented }, widgets.HotkeyLabel{ frame={t=12, l=0}, From bb0d4c410dae7194271ac8df04ce31d9d388ba6d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Mar 2023 00:00:25 -0800 Subject: [PATCH 15/16] update docs --- docs/plugins/buildingplan.rst | 126 ++++++++++++++++++++++++++++------ docs/plugins/overlay.rst | 4 +- 2 files changed, 107 insertions(+), 23 deletions(-) diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index f77e5b590..d6fce19b8 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -18,41 +18,41 @@ for the placed building, it will be created in a suspended state. Buildingplan will periodically scan for appropriate items and attach them. Once all items are attached, the construction job will be unsuspended and a dwarf will come and build the building. If you have the `unsuspend` overlay enabled (it is enabled -by default), then buildingplan-suspended buildings will appear with a ``P`` marker -on the main map, as opposed to the usual ``x`` marker for "regular" suspended -buildings. +by default), then buildingplan-suspended buildings will appear with a ``P`` +marker on the main map, as opposed to the usual ``x`` marker for "regular" +suspended buildings. If you want to impose restrictions on which items are chosen for the buildings, buildingplan has full support for quality and material filters. Before you place -a building, you can select a component item in the list and hit ``f`` or click on -the ``filter`` button next to the item description. This will let you choose your -desired item quality range, whether the item must be decorated, and even which -specific materials the item must be made out of. This lets you create layouts -with a consistent color, if that is part of your design. +a building, you can select a component item in the list and hit ``f`` or click +on the ``filter`` button next to the item description. This will let you choose +your desired item quality range, whether the item must be decorated, and even +which specific materials the item must be made out of. This lets you create +layouts with a consistent color, if that is part of your design. If you just care about the heat sensitivity of the building, you can set the building to be fire- or magma-proof in the placement UI screen or in any item -filter screen, and the restriction will apply to all building items. This makes it -very easy to create magma-safe pump stacks, for example. +filter screen, and the restriction will apply to all building items. This makes +it very easy to create magma-safe pump stacks, for example. Buildingplan works very well in conjuction with other design tools like -`gui/quickfort`, which allow you to apply a building layout from a blueprint. You -can apply very large, complicated layouts, and the buildings will simply be built -when your dwarves get around to producing the needed materials. If you set filters -in the buildingplan UI before applying the blueprint, the filters will be applied -to the blueprint buildings, just as if you had planned them from the buildingplan -placement UI. +`gui/quickfort`, which allow you to apply a building layout from a blueprint. +You can apply very large, complicated layouts, and the buildings will simply be +built when your dwarves get around to producing the needed materials. If you +set filters in the buildingplan UI before applying the blueprint, the filters +will be applied to the blueprint buildings, just as if you had planned them +from the buildingplan placement UI. One way to integrate buildingplan into your gameplay is to create manager workorders to ensure you always have a few blocks/doors/beds/etc. available. You can then place as many of each building as you like. Produced items will be used -to build the planned buildings as they are produced, with minimal space dedicated -to stockpiles. The DFHack `orders` library can help with setting up these manager -workorders for you. +to build the planned buildings as they are produced, with minimal space +dedicated to stockpiles. The DFHack `orders` library can help with setting up +these manager workorders for you. If you do not wish to use the ``buildingplan`` interface, you can turn off the -``buildingplan.planner`` overlay in `gui/overlay`. You should not disable the -``buildingplan`` service entirely in `gui/control-panel` since then existing +``buildingplan.planner`` overlay in `gui/control-panel`. You should not disable +the ``buildingplan`` service entirely in `gui/control-panel` since existing planned buildings in loaded forts will stop functioning. Usage @@ -92,3 +92,87 @@ constructions:: on-new-fortress buildingplan set boulders false on-new-fortress buildingplan set logs false + +Building placement +------------------ + +Once you have selected a building type to build in the vanilla build menu, the +`buildingplan` placement UI appears as an `overlay` widget, covering the +vanilla building placement panel. + +For basic usage, you don't need to change any settings. Just click to place +buildings of the selected type and right click to exit building mode. Any +buildings that require materials that you don't have on hand will be suspended +and built when the items are available. + +When building constructions, you'll get a few extra options, like whether the +construction area should be hollow or what types of stairs you'd like at the +top and bottom of a stairwell. Also, unlike other buildings, it is ok if some +tiles selected in the construction area are not appropriate for building. For +example, if you want to fill an area with flooring, you can select the entire +area, and any tiles with existing buildings or walls will simply be skipped. + +Setting heat safety filters ++++++++++++++++++++++++++++ + +If you specifically need the building to be magma- or fire-safe, click on the +"Building safety" button or hit :kbd:`g` until the desired heat safety is +displayed. This filter applies to all items used to construct the building. + +Setting quality and material filters +++++++++++++++++++++++++++++++++++++ + +If you want to set restrictions on the items chosen to complete the planned +building, you can click on the "filter" button next to the item name or select +the item with the :kbd:`*` and :kbd:`/` keys and hit :kbd:`f` to bring up the +filter dialog. + +You can select whether the item must be decorated, and you can drag the ends of +the "Item quality" slider to set your desired quality range. Note that blocks, +boulders, logs, and bars don't have a quality and the quality options are +disabled for those types. As you change the quality settings, the number of +currently available matched items of each material appears in the materials +list. + +You can click on specific materials to allow only items of those materials when +building the current type of building. You can also allow or disallow entire +categories of materials by clicking on the "Type" options on the left. Note +that it is perfectly fine to choose materials that currently show 0 quantity. +`buildingplan` will patiently watch for items made of materials you have +selected. + +Choosing specific items ++++++++++++++++++++++++ + +If you want to choose specific items, click on the "Choose from items" toggle +or hit :kbd:`i` before placing the building. When you click to place the +building, a dialog will come up that allows you choose which items to use. The +list is sorted by most recently used materials for that building type by +default, but you can change to sort by name or by available quantity by +clicking on the "Sort by" selector or hitting :kbd:`R`. + +You can select the maximum quantity of a specified item by clicking on the item +name or selecting it with the arrow keys and hitting :kbd:`Enter`. You can +instead select items one at a time by Ctrl-clicking (:kbd:`Shift`:kbd:`Right`) +to increment or Ctrl-Shift-clicking (:kbd:`Shift`:kbd:`Left`) to decrement. + +Once you are satisfied with your choices, click on the "Build" button or hit +:kbd:`B` to continue building. Note that you don't have to select all the items +that the building needs. Any remaining items will be automatically chosen from +other available items (or future items if not all items are available yet). If +there are multiple item types to choose for the current building, one dialog +will appear per item type. + +Building status +--------------- + +When viewing a planned building, a separate `overlay` widget appears on the +building info sheet, showing you which items have been attached and which items +are still pending. For the pending items, you can see its position in the +fulfillment queue. If there is a particular building that you need built ASAP, +you can click on the "make top priority" button (or hit :kbd:`Ctrl`:kbd:`T`) to +bump the items for this building to the front of their respective queues. + +Note that each item type and filter configuration has its own queue, so even if +an item is in queue position 1, there may be other queues that snag the needed +item first. diff --git a/docs/plugins/overlay.rst b/docs/plugins/overlay.rst index ab63de67e..313220fd4 100644 --- a/docs/plugins/overlay.rst +++ b/docs/plugins/overlay.rst @@ -7,8 +7,8 @@ overlay The overlay framework manages the on-screen widgets that other tools (including 3rd party plugins and scripts) can register for display. For a graphical -configuration interface, please see `gui/overlay`. If you are a developer who -wants to write an overlay widget, please see the `overlay-dev-guide`. +configuration interface, please see `gui/control-panel`. If you are a developer +who wants to write an overlay widget, please see the `overlay-dev-guide`. Usage ----- From 2220c5c07e5fce1520b67d4092c20ac9c04d61ab Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 6 Mar 2023 00:20:02 -0800 Subject: [PATCH 16/16] editing pass --- docs/plugins/buildingplan.rst | 62 ++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index d6fce19b8..fe3683f77 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -10,32 +10,29 @@ regardless of whether the required materials are available. This allows you to focus purely on design elements when you are laying out your fort, and defers item production concerns to a more convenient time. -Buildingplan is as an alternative to the vanilla building placement UI. It -appears after you have selected the type of building, furniture, or construction -that you want to place in the vanilla build menu. Buildingplan then takes over -for the actual placement step. If any building materials are not available yet -for the placed building, it will be created in a suspended state. Buildingplan -will periodically scan for appropriate items and attach them. Once all items are -attached, the construction job will be unsuspended and a dwarf will come and -build the building. If you have the `unsuspend` overlay enabled (it is enabled -by default), then buildingplan-suspended buildings will appear with a ``P`` -marker on the main map, as opposed to the usual ``x`` marker for "regular" -suspended buildings. +Buildingplan is an alternative to the vanilla building placement UI. It appears +after you have selected the type of building, furniture, or construction that +you want to place in the vanilla build menu. Buildingplan then takes over for +the actual placement step. If the placed building requires materials that +aren't available yet, it will be created in a suspended state. Buildingplan will +periodically scan for appropriate items and attach them to the planned +building. Once all items are attached, the construction job will be unsuspended +and a dwarf will come and build the building. If you have the `unsuspend` +overlay enabled (it is enabled by default), then buildingplan-suspended +buildings will appear with a ``P`` marker on the main map, as opposed to the +usual ``x`` marker for "regular" suspended buildings. If you want to impose restrictions on which items are chosen for the buildings, -buildingplan has full support for quality and material filters. Before you place -a building, you can select a component item in the list and hit ``f`` or click -on the ``filter`` button next to the item description. This will let you choose -your desired item quality range, whether the item must be decorated, and even -which specific materials the item must be made out of. This lets you create -layouts with a consistent color, if that is part of your design. +buildingplan has full support for quality and material filters (see `below +`_). This lets you create layouts with a +consistent color, if that is part of your design. If you just care about the heat sensitivity of the building, you can set the -building to be fire- or magma-proof in the placement UI screen or in any item -filter screen, and the restriction will apply to all building items. This makes -it very easy to create magma-safe pump stacks, for example. +building to be fire- or magma-proof in the placement UI screen. This makes it +very easy to ensure that your pump stacks and floodgates, for example, are +magma-safe. -Buildingplan works very well in conjuction with other design tools like +Buildingplan works well in conjuction with other design tools like `gui/quickfort`, which allow you to apply a building layout from a blueprint. You can apply very large, complicated layouts, and the buildings will simply be built when your dwarves get around to producing the needed materials. If you @@ -47,13 +44,14 @@ One way to integrate buildingplan into your gameplay is to create manager workorders to ensure you always have a few blocks/doors/beds/etc. available. You can then place as many of each building as you like. Produced items will be used to build the planned buildings as they are produced, with minimal space -dedicated to stockpiles. The DFHack `orders` library can help with setting up -these manager workorders for you. +dedicated to stockpiles. The DFHack `orders` library can help with setting +these manager workorders up for you. If you do not wish to use the ``buildingplan`` interface, you can turn off the -``buildingplan.planner`` overlay in `gui/control-panel`. You should not disable -the ``buildingplan`` service entirely in `gui/control-panel` since existing -planned buildings in loaded forts will stop functioning. +``buildingplan.planner`` overlay in `gui/control-panel` (on the "Overlays" +tab). You should not disable the ``buildingplan`` "System service" in +`gui/control-panel` since existing planned buildings in loaded forts will stop +functioning. Usage ----- @@ -70,6 +68,10 @@ Examples Print a report of current settings, which kinds of buildings are planned, and what kinds of materials the buildings are waiting for. +``buildingplan set boulders false`` + When finding items to satisfy "building materials" requirements, don't + select boulders. Use blocks or logs (if enabled) instead. + .. _buildingplan-settings: Global settings @@ -129,15 +131,15 @@ filter dialog. You can select whether the item must be decorated, and you can drag the ends of the "Item quality" slider to set your desired quality range. Note that blocks, -boulders, logs, and bars don't have a quality and the quality options are +boulders, logs, and bars don't have a quality, and the quality options are disabled for those types. As you change the quality settings, the number of -currently available matched items of each material appears in the materials +currently available matched items of each material is adjusted in the materials list. You can click on specific materials to allow only items of those materials when building the current type of building. You can also allow or disallow entire categories of materials by clicking on the "Type" options on the left. Note -that it is perfectly fine to choose materials that currently show 0 quantity. +that it is perfectly fine to choose materials that currently show zero quantity. `buildingplan` will patiently watch for items made of materials you have selected. @@ -168,7 +170,7 @@ Building status When viewing a planned building, a separate `overlay` widget appears on the building info sheet, showing you which items have been attached and which items -are still pending. For the pending items, you can see its position in the +are still pending. For a pending item, you can see its position in the fulfillment queue. If there is a particular building that you need built ASAP, you can click on the "make top priority" button (or hit :kbd:`Ctrl`:kbd:`T`) to bump the items for this building to the front of their respective queues.