filter by whether a slab is engraved

this actually adds an entirely new "specials" filter system that can be
extended later for other types
develop
Myk Taylor 2023-03-15 00:29:27 -07:00
parent c47f068769
commit 4be5ca4e81
No known key found for this signature in database
10 changed files with 135 additions and 26 deletions

@ -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.

@ -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<MaterialInfo>());
}
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
};

@ -9,6 +9,7 @@
#include "df/job_item_vector_id.h"
#include <deque>
#include <set>
typedef std::deque<std::pair<int32_t, int>> Bucket;
typedef std::map<df::job_item_vector_id, std::map<std::string, Bucket>> Tasks;
@ -49,6 +50,6 @@ void set_config_bool(DFHack::PersistentDataItem &c, int index, bool value);
std::vector<df::job_item_vector_id> 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<std::string> &special);
bool isJobReady(DFHack::color_ostream &out, const std::vector<df::job_item *> &jitems);
void finalizeBuilding(DFHack::color_ostream &out, df::building *bld);

@ -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<string> &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<df::item_slabst *>(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))
{

@ -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<ItemFilter> &item_filters, const std::set<std::string> &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<const df::job_item *> &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<const df::job_item *> &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<ItemFilter> filters = deserialize_item_filters(out, serialized);
std::vector<std::string> elems;
split_string(&elems, serialized, "|");
std::vector<ItemFilter> 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<string> 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());
}

@ -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<ItemFilter> & getItemFilters() const { return item_filters; }
const std::set<std::string> & getSpecials() const { return specials; }
private:
DFHack::PersistentDataItem filter_config;
bool choose_items;
std::vector<ItemFilter> item_filters;
std::set<std::string> specials;
};

@ -69,19 +69,35 @@ static vector<ItemFilter> get_item_filters(color_ostream &out, PersistentDataIte
return deserialize_item_filters(out, rawstrs[1]);
}
static string serialize(const vector<vector<df::job_item_vector_id>> &vector_ids, const vector<ItemFilter> &item_filters) {
static set<string> get_specials(color_ostream &out, PersistentDataItem &bld_config) {
vector<string> rawstrs;
split_string(&rawstrs, bld_config.val(), "|");
set<string> ret;
if (rawstrs.size() < 3)
return ret;
vector<string> specials;
split_string(&specials, rawstrs[2], ",");
for (auto & special : specials)
ret.emplace(special);
return ret;
}
static string serialize(const vector<vector<df::job_item_vector_id>> &vector_ids, const DefaultItemFilters &item_filters) {
vector<string> 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<ItemFilter> &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

@ -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<ItemFilter> item_filters;
PlannedBuilding(DFHack::color_ostream &out, df::building *bld, HeatSafety heat, const std::vector<ItemFilter> &item_filters);
const std::set<std::string> 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);

@ -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)

@ -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