diff --git a/docs/changelog.txt b/docs/changelog.txt index 4a32c9b57..e4588e328 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,12 +36,13 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Plugins ## Fixes --@ `buildingplan`: remember choice per building type for whether the player wants to choose specific items -@ `buildingplan`: items are now attached correctly to screw pumps and other multi-item buildings --@ `buildingplan`: you can now attach multiple weapons to spike traps ## Misc Improvements -@ `buildingplan`: can now filter by clay materials +-@ `buildingplan`: remember choice per building type for whether the player wants to choose specific items +-@ `buildingplan`: you can now attach multiple weapons to spike traps +-@ `buildingplan`: can now filter by whether a slab is engraved - `blueprint`: now writes blueprints to the ``dfhack-config/blueprints`` directory - `blueprint-library-guide`: library blueprints have moved from ``blueprints`` to ``hack/data/blueprints`` - player-created blueprints should now go in the ``dfhack-config/blueprints`` folder. please move your existing blueprints from ``blueprints`` to ``dfhack-config/blueprints``. you don't need to move the library blueprints -- those can be safely deleted from the old ``blueprints`` directory. diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index 304b7001e..c2b4431d9 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -394,7 +394,7 @@ static string getBucket(const df::job_item & ji, const PlannedBuilding & pb, int ser << "Hc"; size_t num_materials = item_filter.getMaterials().size(); - if (num_materials == 0 || num_materials >= 9 || item_filter.getMaterialMask().whole) + if (num_materials == 0 || num_materials >= 9 || !item_filter.getMaterialMask().whole) ser << "M9"; else ser << "M" << num_materials; @@ -412,6 +412,9 @@ static string getBucket(const df::job_item & ji, const PlannedBuilding & pb, int ser << ':' << item_filter.serialize(); + for (auto &special : pb.specials) + ser << ':' << special; + return ser.str(); } @@ -596,7 +599,7 @@ static bool addPlannedBuilding(color_ostream &out, df::building *bld) { bld->getCustomType())) return false; BuildingTypeKey key(bld->getType(), bld->getSubtype(), bld->getCustomType()); - PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key).getItemFilters()); + PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key)); return registerPlannedBuilding(out, pb); } @@ -621,7 +624,9 @@ static int scanAvailableItems(color_ostream &out, df::building_type type, int16_ auto &job_items = get_job_items(out, key); if (index < 0 || job_items.size() <= (size_t)index) return 0; - auto &item_filters = get_item_filters(out, key).getItemFilters(); + auto &item_filters = get_item_filters(out, key); + auto &filters = item_filters.getItemFilters(); + auto &specials = item_filters.getSpecials(); auto &jitem = job_items[index]; auto vector_ids = getVectorIds(out, jitem); @@ -630,13 +635,13 @@ 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]) { - ItemFilter filter = item_filters[index]; + ItemFilter filter = 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 (itemPassesScreen(item) && matchesFilters(item, jitem, heat, filter, specials)) { if (item_ids) item_ids->emplace_back(item->id); if (counts) { @@ -939,6 +944,29 @@ static int getHeatSafetyFilter(lua_State *L) { return 1; } +static void setSpecial(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, string special, bool val) { + DEBUG(status,out).print("entering setSpecial\n"); + BuildingTypeKey key(type, subtype, custom); + auto &filters = get_item_filters(out, key); + filters.setSpecial(special, val); + call_buildingplan_lua(&out, "signal_reset"); +} + +static int getSpecials(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); + DEBUG(status,*out).print( + "entering getSpecials building_type=%d subtype=%d custom=%d\n", + type, subtype, custom); + BuildingTypeKey key(type, subtype, custom); + Lua::Push(L, get_item_filters(*out, key).getSpecials()); + return 1; +} + static void setQualityFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index, int decorated, int min_quality, int max_quality) { DEBUG(status,out).print("entering setQualityFilter\n"); @@ -1086,6 +1114,7 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(clearFilter), DFHACK_LUA_FUNCTION(setChooseItems), DFHACK_LUA_FUNCTION(setHeatSafetyFilter), + DFHACK_LUA_FUNCTION(setSpecial), DFHACK_LUA_FUNCTION(setQualityFilter), DFHACK_LUA_FUNCTION(getDescString), DFHACK_LUA_FUNCTION(getQueuePosition), @@ -1102,6 +1131,7 @@ DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getMaterialFilter), DFHACK_LUA_COMMAND(getChooseItems), DFHACK_LUA_COMMAND(getHeatSafetyFilter), + DFHACK_LUA_COMMAND(getSpecials), DFHACK_LUA_COMMAND(getQualityFilter), DFHACK_LUA_END }; diff --git a/plugins/buildingplan/buildingplan.h b/plugins/buildingplan/buildingplan.h index 6aa5e544f..495602b0b 100644 --- a/plugins/buildingplan/buildingplan.h +++ b/plugins/buildingplan/buildingplan.h @@ -9,6 +9,7 @@ #include "df/job_item_vector_id.h" #include +#include typedef std::deque> Bucket; typedef std::map> Tasks; @@ -49,6 +50,6 @@ void set_config_bool(DFHack::PersistentDataItem &c, int index, bool value); std::vector getVectorIds(DFHack::color_ostream &out, const df::job_item *job_item); bool itemPassesScreen(df::item * item); df::job_item getJobItemWithHeatSafety(const df::job_item *job_item, HeatSafety heat); -bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter); +bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter, const std::set &special); bool isJobReady(DFHack::color_ostream &out, const std::vector &jitems); void finalizeBuilding(DFHack::color_ostream &out, df::building *bld); diff --git a/plugins/buildingplan/buildingplan_cycle.cpp b/plugins/buildingplan/buildingplan_cycle.cpp index 41825c7da..803f1f130 100644 --- a/plugins/buildingplan/buildingplan_cycle.cpp +++ b/plugins/buildingplan/buildingplan_cycle.cpp @@ -10,6 +10,7 @@ #include "df/building_design.h" #include "df/item.h" +#include "df/item_slabst.h" #include "df/job.h" #include "df/map_block.h" #include "df/world.h" @@ -58,7 +59,7 @@ df::job_item getJobItemWithHeatSafety(const df::job_item *job_item, HeatSafety h return jitem; } -bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter) { +bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter, const std::set &specials) { // check the properties that are not checked by Job::isSuitableItem() if (job_item->item_type > -1 && job_item->item_type != item->getType()) return false; @@ -77,6 +78,10 @@ bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety h && !item->hasToolUse(job_item->has_tool_use)) return false; + if (item->getType() == df::item_type::SLAB && specials.count("engraved") + && static_cast(item)->engraving_type != df::slab_engraving_type::Memorial) + return false; + df::job_item jitem = getJobItemWithHeatSafety(job_item, heat); return Job::isSuitableItem( @@ -212,7 +217,7 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, auto &pb = planned_buildings.at(id); if (isAccessibleFrom(out, item, job) && matchesFilters(item, jitems[filter_idx], pb.heat_safety, - pb.item_filters[rev_filter_idx]) + pb.item_filters[rev_filter_idx], pb.specials) && Job::attachJobItem(job, item, df::job_item_ref::Hauled, filter_idx)) { diff --git a/plugins/buildingplan/defaultitemfilters.cpp b/plugins/buildingplan/defaultitemfilters.cpp index e866808f8..8af74eff2 100644 --- a/plugins/buildingplan/defaultitemfilters.cpp +++ b/plugins/buildingplan/defaultitemfilters.cpp @@ -31,6 +31,13 @@ static int get_max_quality(const df::job_item *jitem) { return df::item_quality::Masterful; } +static string serialize(const std::vector &item_filters, const std::set &specials) { + std::ostringstream out; + out << serialize_item_filters(item_filters); + out << "|" << join_strings(",", specials); + return out.str(); +} + DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector &jitems) : key(key), choose_items(false) { DEBUG(status,out).print("creating persistent data for filter key %d,%d,%d\n", @@ -44,22 +51,30 @@ DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, for (size_t idx = 0; idx < jitems.size(); ++idx) { item_filters[idx].setMaxQuality(get_max_quality(jitems[idx]), true); } - filter_config.val() = serialize_item_filters(item_filters); + filter_config.val() = serialize(item_filters, specials); } DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &filter_config, const std::vector &jitems) : key(getKey(filter_config)), filter_config(filter_config) { choose_items = get_config_bool(filter_config, FILTER_CONFIG_CHOOSE_ITEMS); auto &serialized = filter_config.val(); - DEBUG(status,out).print("deserializing 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::vector filters = deserialize_item_filters(out, serialized); + std::vector elems; + split_string(&elems, serialized, "|"); + std::vector filters = deserialize_item_filters(out, elems[0]); if (filters.size() != jitems.size()) { WARN(status,out).print("ignoring invalid filters_str for key %d,%d,%d: '%s'\n", std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str()); item_filters.resize(jitems.size()); } else item_filters = filters; + if (elems.size() > 1) { + vector specs; + split_string(&specs, elems[1], ","); + for (auto & special : specs) + specials.emplace(special); + } } void DefaultItemFilters::setChooseItems(bool choose) { @@ -67,6 +82,14 @@ void DefaultItemFilters::setChooseItems(bool choose) { set_config_bool(filter_config, FILTER_CONFIG_CHOOSE_ITEMS, choose); } +void DefaultItemFilters::setSpecial(const std::string &special, bool val) { + if (val) + specials.emplace(special); + else + specials.erase(special); + filter_config.val() = serialize(item_filters, specials); +} + void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index) { if (index < 0 || item_filters.size() <= (size_t)index) { WARN(status,out).print("invalid index for filter key %d,%d,%d: %d\n", @@ -75,7 +98,7 @@ void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFil } item_filters[index] = filter; - filter_config.val() = serialize_item_filters(item_filters); + filter_config.val() = serialize(item_filters, specials); DEBUG(status,out).print("updated item filter and persisted for key %d,%d,%d: %s\n", std::get<0>(key), std::get<1>(key), std::get<2>(key), filter_config.val().c_str()); } diff --git a/plugins/buildingplan/defaultitemfilters.h b/plugins/buildingplan/defaultitemfilters.h index 37ebdcaae..d7ed12a7b 100644 --- a/plugins/buildingplan/defaultitemfilters.h +++ b/plugins/buildingplan/defaultitemfilters.h @@ -16,12 +16,15 @@ public: void setChooseItems(bool choose); void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index); + void setSpecial(const std::string &special, bool val); bool getChooseItems() const { return choose_items; } const std::vector & getItemFilters() const { return item_filters; } + const std::set & getSpecials() const { return specials; } private: DFHack::PersistentDataItem filter_config; bool choose_items; std::vector item_filters; + std::set specials; }; diff --git a/plugins/buildingplan/plannedbuilding.cpp b/plugins/buildingplan/plannedbuilding.cpp index aef55edc3..60e99e073 100644 --- a/plugins/buildingplan/plannedbuilding.cpp +++ b/plugins/buildingplan/plannedbuilding.cpp @@ -69,19 +69,35 @@ static vector get_item_filters(color_ostream &out, PersistentDataIte return deserialize_item_filters(out, rawstrs[1]); } -static string serialize(const vector> &vector_ids, const vector &item_filters) { +static set get_specials(color_ostream &out, PersistentDataItem &bld_config) { + vector rawstrs; + split_string(&rawstrs, bld_config.val(), "|"); + set ret; + if (rawstrs.size() < 3) + return ret; + vector specials; + split_string(&specials, rawstrs[2], ","); + for (auto & special : specials) + ret.emplace(special); + return ret; +} + +static string serialize(const vector> &vector_ids, const DefaultItemFilters &item_filters) { vector joined; for (auto &vec_list : vector_ids) { joined.emplace_back(join_strings(",", vec_list)); } std::ostringstream out; - out << join_strings(";", joined) << "|" << serialize_item_filters(item_filters); + out << join_strings(";", joined); + out << "|" << serialize_item_filters(item_filters.getItemFilters()); + out << "|" << join_strings(",", item_filters.getSpecials()); return out.str(); } -PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafety heat, const vector &item_filters) +PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafety heat, const DefaultItemFilters &item_filters) : id(bld->id), vector_ids(get_vector_ids(out, id)), heat_safety(heat), - item_filters(item_filters) { + item_filters(item_filters.getItemFilters()), + specials(item_filters.getSpecials()) { DEBUG(status,out).print("creating persistent data for building %d\n", id); bld_config = World::AddPersistentData(BLD_CONFIG_KEY); set_config_val(bld_config, BLD_CONFIG_ID, id); @@ -95,6 +111,7 @@ PlannedBuilding::PlannedBuilding(color_ostream &out, PersistentDataItem &bld_con vector_ids(deserialize_vector_ids(out, bld_config)), heat_safety((HeatSafety)get_config_val(bld_config, BLD_CONFIG_HEAT)), item_filters(get_item_filters(out, bld_config)), + specials(get_specials(out, bld_config)), bld_config(bld_config) { } // Ensure the building still exists and is in a valid state. It can disappear diff --git a/plugins/buildingplan/plannedbuilding.h b/plugins/buildingplan/plannedbuilding.h index 59dc24a79..703d4246d 100644 --- a/plugins/buildingplan/plannedbuilding.h +++ b/plugins/buildingplan/plannedbuilding.h @@ -1,6 +1,7 @@ #pragma once #include "buildingplan.h" +#include "defaultitemfilters.h" #include "itemfilter.h" #include "Core.h" @@ -21,7 +22,9 @@ public: const std::vector item_filters; - PlannedBuilding(DFHack::color_ostream &out, df::building *bld, HeatSafety heat, const std::vector &item_filters); + const std::set specials; + + PlannedBuilding(DFHack::color_ostream &out, df::building *bld, HeatSafety heat, const DefaultItemFilters &item_filters); PlannedBuilding(DFHack::color_ostream &out, DFHack::PersistentDataItem &bld_config); void remove(DFHack::color_ostream &out); diff --git a/plugins/lua/buildingplan/filterselection.lua b/plugins/lua/buildingplan/filterselection.lua index e923c4b5b..84b5c46ea 100644 --- a/plugins/lua/buildingplan/filterselection.lua +++ b/plugins/lua/buildingplan/filterselection.lua @@ -432,6 +432,15 @@ function QualityAndMaterialsPage:refresh() else summary = 'Any ' .. summary end + local specials = buildingplan.getSpecials(uibs.building_type, uibs.building_subtype, uibs.custom_type) + if next(specials) then + local specials_list = {} + for special in pairs(specials) do + table.insert(specials_list, special) + end + summary = summary .. ' [' .. table.concat(specials_list, ', ') .. ']' + end + local quality = buildingplan.getQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) subviews.decorated:setOption(quality.decorated ~= 0) subviews.min_quality:setOption(quality.min_quality) diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua index d4a94a01d..df49fe287 100644 --- a/plugins/lua/buildingplan/planneroverlay.lua +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -81,14 +81,18 @@ local function cur_building_has_no_area() return filters and filters[1] and (not filters[1].quantity or filters[1].quantity > 0) end +local function is_construction() + return uibs.building_type == df.building_type.Construction +end + local function is_plannable() return get_cur_filters() and - not (uibs.building_type == df.building_type.Construction - and uibs.building_subtype == df.construction_type.TrackNSEW) + not (is_construction() and + uibs.building_subtype == df.construction_type.TrackNSEW) end -local function is_construction() - return uibs.building_type == df.building_type.Construction +local function is_slab() + return uibs.building_type == df.building_type.Slab end local function is_stairs() @@ -346,6 +350,16 @@ function PlannerOverlay:init() options={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, on_change=function(val) weapon_quantity = val end, }, + widgets.ToggleHotkeyLabel { + view_id='engraved', + frame={t=5, l=4}, + key='CUSTOM_T', + label='Engraved only:', + visible=is_slab, + on_change=function(val) + buildingplan.setSpecial(uibs.building_type, uibs.building_subtype, uibs.custom_type, 'engraved', val) + end, + }, widgets.Label{ frame={b=3, l=17}, text={ @@ -637,10 +651,13 @@ function PlannerOverlay:onRenderFrame(dc, rect) if reset_counts_flag then self:reset() - self.subviews.choose:setOption(require('plugins.buildingplan').getChooseItems( + local buildingplan = require('plugins.buildingplan') + self.subviews.engraved:setOption(buildingplan.getSpecials( + uibs.building_type, uibs.building_subtype, uibs.custom_type).engraved or false) + self.subviews.choose:setOption(buildingplan.getChooseItems( + uibs.building_type, uibs.building_subtype, uibs.custom_type)) + self.subviews.safety:setOption(buildingplan.getHeatSafetyFilter( uibs.building_type, uibs.building_subtype, uibs.custom_type)) - self.subviews.safety:setOption(require('plugins.buildingplan').getHeatSafetyFilter( - uibs.building_type, uibs.building_subtype, uibs.custom_type)) end local selection_pos = self.saved_selection_pos or uibs.selection_pos