Merge remote-tracking branch 'myk002/buildingplan_refactor2_squashed' into develop

develop
lethosor 2020-10-23 13:51:54 -04:00
commit 4301252ddf
No known key found for this signature in database
GPG Key ID: 76A269552F4F58C1
11 changed files with 737 additions and 530 deletions

@ -3784,9 +3784,10 @@ buildingplan
Native functions provided by the `buildingplan` plugin:
* ``bool isPlannableBuilding(df::building_type type)`` returns whether the building type is handled by buildingplan
* ``bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the building type is handled by buildingplan
* ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list
* ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now.
* ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled.
burrows
=======

@ -136,8 +136,8 @@ namespace DFHack
return findProduct(info.material, name);
}
std::string getToken();
std::string toString(uint16_t temp = 10015, bool named = true);
std::string getToken() const;
std::string toString(uint16_t temp = 10015, bool named = true) const;
bool isAnyCloth();

@ -305,7 +305,7 @@ bool MaterialInfo::findProduct(df::material *material, const std::string &name)
return decode(-1);
}
std::string MaterialInfo::getToken()
std::string MaterialInfo::getToken() const
{
if (isNone())
return "NONE";
@ -333,7 +333,7 @@ std::string MaterialInfo::getToken()
}
}
std::string MaterialInfo::toString(uint16_t temp, bool named)
std::string MaterialInfo::toString(uint16_t temp, bool named) const
{
if (isNone())
return "any";

@ -1,16 +1,22 @@
#include "buildingplan-lib.h"
#include <cstdarg>
#include "Core.h"
using namespace DFHack;
bool show_debugging = false;
void debug(const std::string &msg)
void debug(const char *fmt, ...)
{
if (!show_debugging)
return;
color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG: " << msg << endl;
out.print("DEBUG(buildingplan): ");
va_list args;
va_start(args, fmt);
out.vprint(fmt, args);
va_end(args);
out.print("\n");
}

@ -3,6 +3,6 @@
#include "buildingplan-planner.h"
#include "buildingplan-rooms.h"
void debug(const std::string &msg);
void debug(const char *fmt, ...) Wformat(printf,1,2);
extern bool show_debugging;

@ -1,185 +1,269 @@
#include <functional>
#include <climits> // for CHAR_BIT
#include "df/building_design.h"
#include "df/building_doorst.h"
#include "df/building_type.h"
#include "df/general_ref_building_holderst.h"
#include "df/job_item.h"
#include "df/building_doorst.h"
#include "df/building_design.h"
#include "df/ui_build_selector.h"
#include "modules/Job.h"
#include "modules/Buildings.h"
#include "modules/Gui.h"
#include "modules/Job.h"
#include "uicommon.h"
#include "buildingplan-planner.h"
#include "buildingplan-lib.h"
#include "uicommon.h"
/*
* ItemFilter
*/
static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints";
ItemFilter::ItemFilter() :
min_quality(df::item_quality::Ordinary),
max_quality(df::item_quality::Artifact),
decorated_only(false),
valid(true)
{
clear(); // mat_mask is not cleared by default (see issue #1047)
}
/*
* ItemFilter
*/
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat)
ItemFilter::ItemFilter()
{
return (mat_mask.whole) ? mat.matches(mat_mask) : true;
clear();
}
bool ItemFilter::matches(const df::dfhack_material_category mask) const
void ItemFilter::clear()
{
return mask.whole & mat_mask.whole;
min_quality = df::item_quality::Ordinary;
max_quality = df::item_quality::Masterful;
decorated_only = false;
clearMaterialMask();
materials.clear();
}
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
bool ItemFilter::deserialize(PersistentDataItem &config)
{
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
clear();
bool ItemFilter::matches(df::item *item)
{
if (item->getQuality() < min_quality || item->getQuality() > max_quality)
std::vector<std::string> tokens;
split_string(&tokens, config.val(), "/");
if (tokens.size() != 2)
{
debug("invalid ItemFilter serialization: '%s'", config.val().c_str());
return false;
}
if (decorated_only && !item->hasImprovements())
if (!deserializeMaterialMask(tokens[0]) || !deserializeMaterials(tokens[1]))
return false;
auto imattype = item->getActualMaterial();
auto imatindex = item->getActualMaterialIndex();
auto item_mat = DFHack::MaterialInfo(imattype, imatindex);
return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat);
setMinQuality(config.ival(2) - 1);
setMaxQuality(config.ival(4) - 1);
decorated_only = config.ival(3) - 1;
return true;
}
std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); }
bool ItemFilter::deserializeMaterialMask(std::string ser)
{
if (ser.empty())
return true;
std::vector<std::string> ItemFilter::getMaterialFilterAsVector()
if (!parseJobMaterialCategory(&mat_mask, ser))
{
debug("invalid job material category serialization: '%s'", ser.c_str());
return false;
}
return true;
}
bool ItemFilter::deserializeMaterials(std::string ser)
{
std::vector<std::string> descriptions;
if (ser.empty())
return true;
transform_(materials, descriptions, material_to_string_fn);
std::vector<std::string> mat_names;
split_string(&mat_names, ser, ",");
for (auto m = mat_names.begin(); m != mat_names.end(); m++)
{
DFHack::MaterialInfo material;
if (!material.find(*m) || !material.isValid())
{
debug("invalid material name serialization: '%s'", ser.c_str());
return false;
}
materials.push_back(material);
}
return true;
}
if (descriptions.size() == 0)
bitfield_to_string(&descriptions, mat_mask);
void ItemFilter::serialize(PersistentDataItem &config) const
{
std::ostringstream ser;
ser << bitfield_to_string(mat_mask, ",") << "/";
if (!materials.empty())
{
ser << materials[0].getToken();
for (size_t i = 1; i < materials.size(); ++i)
ser << "," << materials[i].getToken();
}
config.val() = ser.str();
config.ival(2) = min_quality + 1;
config.ival(4) = max_quality + 1;
config.ival(3) = static_cast<int>(decorated_only) + 1;
}
if (descriptions.size() == 0)
descriptions.push_back("any");
void ItemFilter::clearMaterialMask()
{
mat_mask.whole = 0;
}
return descriptions;
void ItemFilter::addMaterialMask(uint32_t mask)
{
mat_mask.whole |= mask;
}
std::string ItemFilter::getMaterialFilterAsSerial()
void ItemFilter::setMaterials(std::vector<DFHack::MaterialInfo> materials)
{
std::string str;
this->materials = materials;
}
str.append(bitfield_to_string(mat_mask, ","));
str.append("/");
if (materials.size() > 0)
static void clampItemQuality(df::item_quality *quality)
{
if (*quality > item_quality::Artifact)
{
for (size_t i = 0; i < materials.size(); i++)
str.append(materials[i].getToken() + ",");
if (str[str.size()-1] == ',')
str.resize(str.size () - 1);
debug("clamping quality to Artifact");
*quality = item_quality::Artifact;
}
if (*quality < item_quality::Ordinary)
{
debug("clamping quality to Ordinary");
*quality = item_quality::Ordinary;
}
}
return str;
void ItemFilter::setMinQuality(int quality)
{
min_quality = static_cast<df::item_quality>(quality);
clampItemQuality(&min_quality);
if (max_quality < min_quality)
max_quality = min_quality;
}
bool ItemFilter::parseSerializedMaterialTokens(std::string str)
void ItemFilter::setMaxQuality(int quality)
{
valid = false;
std::vector<std::string> tokens;
split_string(&tokens, str, "/");
max_quality = static_cast<df::item_quality>(quality);
clampItemQuality(&max_quality);
if (max_quality < min_quality)
min_quality = max_quality;
}
if (tokens.size() > 0 && !tokens[0].empty())
{
if (!parseJobMaterialCategory(&mat_mask, tokens[0]))
return false;
}
void ItemFilter::incMinQuality() { setMinQuality(min_quality + 1); }
void ItemFilter::decMinQuality() { setMinQuality(min_quality - 1); }
void ItemFilter::incMaxQuality() { setMaxQuality(max_quality + 1); }
void ItemFilter::decMaxQuality() { setMaxQuality(max_quality - 1); }
if (tokens.size() > 1 && !tokens[1].empty())
{
std::vector<std::string> mat_names;
split_string(&mat_names, tokens[1], ",");
for (auto m = mat_names.begin(); m != mat_names.end(); m++)
{
DFHack::MaterialInfo material;
if (!material.find(*m) || !material.isValid())
return false;
void ItemFilter::toggleDecoratedOnly() { decorated_only = !decorated_only; }
materials.push_back(material);
}
}
static std::string material_to_string_fn(const MaterialInfo &m) { return m.toString(); }
valid = true;
return true;
uint32_t ItemFilter::getMaterialMask() const { return mat_mask.whole; }
std::vector<std::string> ItemFilter::getMaterials() const
{
std::vector<std::string> descriptions;
transform_(materials, descriptions, material_to_string_fn);
if (descriptions.size() == 0)
bitfield_to_string(&descriptions, mat_mask);
if (descriptions.size() == 0)
descriptions.push_back("any");
return descriptions;
}
std::string ItemFilter::getMinQuality()
std::string ItemFilter::getMinQuality() const
{
return ENUM_KEY_STR(item_quality, min_quality);
}
std::string ItemFilter::getMaxQuality()
std::string ItemFilter::getMaxQuality() const
{
return ENUM_KEY_STR(item_quality, max_quality);
}
bool ItemFilter::isValid()
bool ItemFilter::getDecoratedOnly() const
{
return valid;
return decorated_only;
}
void ItemFilter::clear()
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) const
{
mat_mask.whole = 0;
materials.clear();
return mat_mask.whole ? mat.matches(mat_mask) : true;
}
/*
* PlannedBuilding
*/
bool ItemFilter::matches(df::dfhack_material_category mask) const
{
return mask.whole & mat_mask.whole;
}
PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter)
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
{
this->building = building;
this->filter = *filter;
pos = df::coord(building->centerx, building->centery, building->z);
config = DFHack::World::AddPersistentData("buildingplan/constraints");
config.val() = filter->getMaterialFilterAsSerial();
config.ival(1) = building->id;
config.ival(2) = filter->min_quality + 1;
config.ival(3) = static_cast<int>(filter->decorated_only) + 1;
config.ival(4) = filter->max_quality + 1;
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out)
bool ItemFilter::matches(df::item *item) const
{
this->config = config;
if (item->getQuality() < min_quality || item->getQuality() > max_quality)
return false;
if (!filter.parseSerializedMaterialTokens(config.val()))
{
out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str());
return;
}
if (decorated_only && !item->hasImprovements())
return false;
building = df::building::find(config.ival(1));
if (!building)
return;
auto imattype = item->getActualMaterial();
auto imatindex = item->getActualMaterialIndex();
auto item_mat = DFHack::MaterialInfo(imattype, imatindex);
return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat);
}
/*
* PlannedBuilding
*/
pos = df::coord(building->centerx, building->centery, building->z);
filter.min_quality = static_cast<df::item_quality>(config.ival(2) - 1);
filter.max_quality = static_cast<df::item_quality>(config.ival(4) - 1);
filter.decorated_only = config.ival(3) - 1;
static std::vector<ItemFilter> deserializeFilters(PersistentDataItem &config)
{
// simplified implementation while we can assume there is only one filter
std::vector<ItemFilter> ret;
ItemFilter itemFilter;
itemFilter.deserialize(config);
ret.push_back(itemFilter);
return ret;
}
static size_t getNumFilters(BuildingTypeKey key)
{
// TODO: get num filters in Lua when we handle all building types
return 1;
}
PlannedBuilding::PlannedBuilding(df::building *building, const std::vector<ItemFilter> &filters)
: building(building),
building_id(building->id),
filters(filters)
{
config = DFHack::World::AddPersistentData(planned_building_persistence_key_v1);
config.ival(1) = building_id;
// assume all filter vectors are length 1 for now
filters[0].serialize(config);
}
PlannedBuilding::PlannedBuilding(PersistentDataItem &config)
: config(config),
building(df::building::find(config.ival(1))),
building_id(config.ival(1)),
filters(deserializeFilters(config))
{ }
bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
{
decltype(items_vector->begin()) closest_item;
@ -187,7 +271,7 @@ bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++)
{
auto item = *item_iter;
if (!filter.matches(item))
if (!filters[0].matches(item))
continue;
auto pos = item->pos;
@ -255,68 +339,83 @@ bool PlannedBuilding::assignItem(df::item *item)
return true;
}
bool PlannedBuilding::isValid()
// Ensure the building still exists and is in a valid state. It can disappear
// for lots of reasons, such as running the game with the buildingplan plugin
// disabled, manually removing the building, modifying it via the API, etc.
bool PlannedBuilding::isValid() const
{
bool valid = filter.isValid() &&
building && Buildings::findAtTile(pos) == building &&
building->getBuildStage() == 0;
if (!valid)
remove();
return valid;
return building && df::building::find(building_id)
&& building->getBuildStage() == 0;
}
df::building_type PlannedBuilding::getType()
void PlannedBuilding::remove()
{
return building->getType();
DFHack::World::DeletePersistentData(config);
building = NULL;
}
bool PlannedBuilding::isCurrentlySelectedBuilding()
df::building * PlannedBuilding::getBuilding()
{
return isValid() && (building == df::global::world->selected_building);
return building;
}
ItemFilter *PlannedBuilding::getFilter()
const std::vector<ItemFilter> & PlannedBuilding::getFilters() const
{
return &filter;
return filters;
}
void PlannedBuilding::remove()
/*
* BuildingTypeKey
*/
BuildingTypeKey toBuildingTypeKey(
df::building_type btype, int16_t subtype, int32_t custom)
{
DFHack::World::DeletePersistentData(config);
return std::make_tuple(btype, subtype, custom);
}
/*
* Planner
*/
BuildingTypeKey toBuildingTypeKey(df::building *bld)
{
return std::make_tuple(
bld->getType(), bld->getSubtype(), bld->getCustomType());
}
Planner::Planner() { }
BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs)
{
return std::make_tuple(
uibs->building_type, uibs->building_subtype, uibs->custom_type);
}
bool Planner::isPlannableBuilding(const df::building_type type) const
// rotates a size_t value left by count bits
// assumes count is not 0 or >= size_t_bits
// replace this with std::rotl when we move to C++20
static std::size_t rotl_size_t(size_t val, uint32_t count)
{
return item_for_building_type.find(type) != item_for_building_type.end();
static const int size_t_bits = CHAR_BIT * sizeof(std::size_t);
return val << count | val >> (size_t_bits - count);
}
void Planner::reset(color_ostream &out)
std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const
{
planned_buildings.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/constraints");
// cast first param to appease gcc-4.8, which is missing the enum
// specializations for std::hash
std::size_t h1 = std::hash<int32_t>()(static_cast<int32_t>(std::get<0>(key)));
std::size_t h2 = std::hash<int16_t>()(std::get<1>(key));
std::size_t h3 = std::hash<int32_t>()(std::get<2>(key));
for (auto i = items.begin(); i != items.end(); i++)
{
PlannedBuilding pb(*i, out);
if (pb.isValid())
planned_buildings.push_back(pb);
}
return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16);
}
/*
* Planner
*/
void Planner::initialize()
{
#define add_building_type(btype, itype) \
item_for_building_type[df::building_type::btype] = df::item_type::itype; \
default_item_filters[df::building_type::btype] = ItemFilter(); \
available_item_vectors[df::item_type::itype] = std::vector<df::item *>(); \
is_relevant_item_type[df::item_type::itype] = true; \
@ -354,53 +453,82 @@ void Planner::initialize()
#undef add_building_type
}
void Planner::addPlannedBuilding(df::building *bld)
void Planner::reset()
{
for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++)
debug("resetting Planner state");
default_item_filters.clear();
planned_buildings.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, planned_building_persistence_key_v1);
debug("found data for %zu planned buildings", items.size());
for (auto i = items.begin(); i != items.end(); i++)
{
(*iter)->flags.bits.suspend = true;
}
PlannedBuilding pb(*i);
if (!pb.isValid())
{
pb.remove();
continue;
}
PlannedBuilding pb(bld, &default_item_filters[bld->getType()]);
planned_buildings.push_back(pb);
planned_buildings.push_back(pb);
}
}
void Planner::doCycle()
void Planner::addPlannedBuilding(df::building *bld)
{
debug("Running Cycle");
if (planned_buildings.size() == 0)
auto item_filters = getItemFilters(toBuildingTypeKey(bld)).get();
// not a supported type
if (item_filters.empty())
{
debug("failed to add building: unsupported type");
return;
}
debug("Planned count: " + int_to_string(planned_buildings.size()));
// protect against multiple registrations
if (getPlannedBuilding(bld))
{
debug("building already registered");
return;
}
gather_available_items();
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
PlannedBuilding pb(bld, item_filters);
if (pb.isValid())
{
if (building_iter->isValid())
{
if (show_debugging)
debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType()));
for (auto job : bld->jobs)
job->flags.bits.suspend = true;
auto required_item_type = item_for_building_type[building_iter->getType()];
auto items_vector = &available_item_vectors[required_item_type];
if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector))
{
debug("Unable to allocate an item");
++building_iter;
continue;
}
}
debug("Removing building plan");
building_iter = planned_buildings.erase(building_iter);
planned_buildings.push_back(pb);
}
else
{
pb.remove();
}
}
PlannedBuilding * Planner::getPlannedBuilding(df::building *bld)
{
for (auto & pb : planned_buildings)
{
if (pb.getBuilding() == bld)
return &pb;
}
return NULL;
}
bool Planner::isPlannableBuilding(BuildingTypeKey key)
{
return item_for_building_type.count(std::get<0>(key)) > 0;
}
bool Planner::allocatePlannedBuilding(df::building_type type)
bool Planner::allocatePlannedBuilding(BuildingTypeKey key)
{
coord32_t cursor;
if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z))
return false;
auto type = std::get<0>(key);
auto newinst = Buildings::allocInstance(cursor.get_coord16(), type);
if (!newinst)
return false;
@ -430,53 +558,48 @@ bool Planner::allocatePlannedBuilding(df::building_type type)
return true;
}
PlannedBuilding *Planner::getSelectedPlannedBuilding()
Planner::ItemFiltersWrapper Planner::getItemFilters(BuildingTypeKey key)
{
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++)
{
if (building_iter->isCurrentlySelectedBuilding())
{
return &(*building_iter);
}
}
static std::vector<ItemFilter> empty_vector;
static const ItemFiltersWrapper empty_ret(empty_vector);
return nullptr;
size_t nfilters = getNumFilters(key);
if (nfilters < 1)
return empty_ret;
while (default_item_filters[key].size() < nfilters)
default_item_filters[key].push_back(ItemFilter());
return ItemFiltersWrapper(default_item_filters[key]);
}
void Planner::removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); }
ItemFilter *Planner::getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; }
void Planner::adjustMinQuality(df::building_type type, int amount)
void Planner::doCycle()
{
auto min_quality = &getDefaultItemFilterForType(type)->min_quality;
*min_quality = static_cast<df::item_quality>(*min_quality + amount);
boundsCheckItemQuality(min_quality);
auto max_quality = &getDefaultItemFilterForType(type)->max_quality;
if (*min_quality > *max_quality)
(*max_quality) = *min_quality;
debug("Running Cycle");
if (planned_buildings.size() == 0)
return;
}
debug("Planned count: %zu", planned_buildings.size());
void Planner::adjustMaxQuality(df::building_type type, int amount)
{
auto max_quality = &getDefaultItemFilterForType(type)->max_quality;
*max_quality = static_cast<df::item_quality>(*max_quality + amount);
boundsCheckItemQuality(max_quality);
auto min_quality = &getDefaultItemFilterForType(type)->min_quality;
if (*max_quality < *min_quality)
(*min_quality) = *max_quality;
}
gather_available_items();
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
{
if (building_iter->isValid())
{
auto type = building_iter->getBuilding()->getType();
debug("Trying to allocate %s", enum_item_key_str(type));
void Planner::boundsCheckItemQuality(item_quality::item_quality *quality)
{
*quality = static_cast<df::item_quality>(*quality);
if (*quality > item_quality::Artifact)
(*quality) = item_quality::Artifact;
if (*quality < item_quality::Ordinary)
(*quality) = item_quality::Ordinary;
auto required_item_type = item_for_building_type[type];
auto items_vector = &available_item_vectors[required_item_type];
if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector))
{
debug("Unable to allocate an item");
++building_iter;
continue;
}
}
debug("Removing building plan");
building_iter->remove();
building_iter = planned_buildings.erase(building_iter);
}
}
void Planner::gather_available_items()

@ -1,99 +1,131 @@
#pragma once
#include "df/item_quality.h"
#include <unordered_map>
#include "df/building.h"
#include "df/dfhack_material_category.h"
#include "df/item_quality.h"
#include "df/job_item.h"
#include "modules/Materials.h"
#include "modules/Persistence.h"
struct ItemFilter
class ItemFilter
{
public:
ItemFilter();
void clear();
bool deserialize(DFHack::PersistentDataItem &config);
void serialize(DFHack::PersistentDataItem &config) const;
void addMaterialMask(uint32_t mask);
void clearMaterialMask();
void setMaterials(std::vector<DFHack::MaterialInfo> materials);
void incMinQuality();
void decMinQuality();
void incMaxQuality();
void decMaxQuality();
void toggleDecoratedOnly();
uint32_t getMaterialMask() const;
std::vector<std::string> getMaterials() const;
std::string getMinQuality() const;
std::string getMaxQuality() const;
bool getDecoratedOnly() const;
bool matches(df::dfhack_material_category mask) const;
bool matches(DFHack::MaterialInfo &material) const;
bool matches(df::item *item) const;
private:
df::dfhack_material_category mat_mask;
std::vector<DFHack::MaterialInfo> materials;
df::item_quality min_quality;
df::item_quality max_quality;
bool decorated_only;
ItemFilter();
bool matchesMask(DFHack::MaterialInfo &mat);
bool matches(const df::dfhack_material_category mask) const;
bool matches(DFHack::MaterialInfo &material) const;
bool matches(df::item *item);
std::vector<std::string> getMaterialFilterAsVector();
std::string getMaterialFilterAsSerial();
bool parseSerializedMaterialTokens(std::string str);
std::string getMinQuality();
std::string getMaxQuality();
bool isValid();
void clear();
private:
bool valid;
bool deserializeMaterialMask(std::string ser);
bool deserializeMaterials(std::string ser);
void setMinQuality(int quality);
void setMaxQuality(int quality);
bool matchesMask(DFHack::MaterialInfo &mat) const;
};
class PlannedBuilding
{
public:
PlannedBuilding(df::building *building, ItemFilter *filter);
PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out);
PlannedBuilding(df::building *building, const std::vector<ItemFilter> &filters);
PlannedBuilding(DFHack::PersistentDataItem &config);
bool assignClosestItem(std::vector<df::item *> *items_vector);
bool assignItem(df::item *item);
bool isValid();
bool isValid() const;
void remove();
df::building_type getType();
bool isCurrentlySelectedBuilding();
ItemFilter *getFilter();
df::building * getBuilding();
const std::vector<ItemFilter> & getFilters() const;
private:
df::building *building;
DFHack::PersistentDataItem config;
df::coord pos;
ItemFilter filter;
df::building *building;
df::building::key_field_type building_id;
std::vector<ItemFilter> filters;
};
// building type, subtype, custom
typedef std::tuple<df::building_type, int16_t, int32_t> BuildingTypeKey;
BuildingTypeKey toBuildingTypeKey(
df::building_type btype, int16_t subtype, int32_t custom);
BuildingTypeKey toBuildingTypeKey(df::building *bld);
BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs);
struct BuildingTypeKeyHash
{
std::size_t operator() (const BuildingTypeKey & key) const;
};
class Planner
{
public:
Planner();
bool isPlannableBuilding(const df::building_type type) const;
void reset(DFHack::color_ostream &out);
class ItemFiltersWrapper
{
public:
ItemFiltersWrapper(std::vector<ItemFilter> & item_filters)
: item_filters(item_filters) { }
std::vector<ItemFilter>::reverse_iterator rbegin() const { return item_filters.rbegin(); }
std::vector<ItemFilter>::reverse_iterator rend() const { return item_filters.rend(); }
const std::vector<ItemFilter> & get() const { return item_filters; }
private:
std::vector<ItemFilter> &item_filters;
};
void initialize();
void reset();
bool allocatePlannedBuilding(BuildingTypeKey key);
void addPlannedBuilding(df::building *bld);
PlannedBuilding *getPlannedBuilding(df::building *bld);
void doCycle();
bool allocatePlannedBuilding(df::building_type type);
PlannedBuilding *getSelectedPlannedBuilding();
bool isPlannableBuilding(BuildingTypeKey key);
void removeSelectedPlannedBuilding();
// returns an empty vector if the type is not supported
ItemFiltersWrapper getItemFilters(BuildingTypeKey key);
ItemFilter *getDefaultItemFilterForType(df::building_type type);
void adjustMinQuality(df::building_type type, int amount);
void adjustMaxQuality(df::building_type type, int amount);
void doCycle();
private:
std::map<df::building_type, df::item_type> item_for_building_type;
std::map<df::building_type, ItemFilter> default_item_filters;
std::unordered_map<BuildingTypeKey,
std::vector<ItemFilter>,
BuildingTypeKeyHash> default_item_filters;
std::map<df::item_type, std::vector<df::item *>> available_item_vectors;
std::map<df::item_type, bool> is_relevant_item_type; //Needed for fast check when looping over all items
std::vector<PlannedBuilding> planned_buildings;
void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality);
void gather_available_items();
};

@ -1,5 +1,5 @@
#include "buildingplan-lib.h"
#include <unordered_map>
#include "df/entity_position.h"
#include "df/interface_key.h"
#include "df/ui_build_selector.h"
@ -14,6 +14,7 @@
#include "uicommon.h"
#include "listcolumn.h"
#include "buildingplan-lib.h"
DFHACK_PLUGIN("buildingplan");
#define PLUGIN_VERSION 0.15
@ -24,18 +25,15 @@ REQUIRE_GLOBAL(world);
#define MAX_MASK 10
#define MAX_MATERIAL 21
using namespace DFHack;
using namespace df::enums;
bool show_help = false;
bool quickfort_mode = false;
bool in_dummy_screen = false;
std::map<df::building_type, bool> planmode_enabled;
std::unordered_map<BuildingTypeKey, bool, BuildingTypeKeyHash> planmode_enabled;
class ViewscreenChooseMaterial : public dfhack_viewscreen
{
public:
ViewscreenChooseMaterial(ItemFilter *filter);
ViewscreenChooseMaterial(ItemFilter &filter);
void feed(set<df::interface_key> *input);
@ -47,14 +45,12 @@ private:
ListColumn<df::dfhack_material_category> masks_column;
ListColumn<MaterialInfo> materials_column;
int selected_column;
ItemFilter *filter;
df::building_type btype;
ItemFilter &filter;
void addMaskEntry(df::dfhack_material_category &mask, const std::string &text)
{
auto entry = ListEntry<df::dfhack_material_category>(pad_string(text, MAX_MASK, false), mask);
if (filter->matches(mask))
if (filter.matches(mask))
entry.selected = true;
masks_column.add(entry);
@ -131,7 +127,7 @@ private:
material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i);
auto name = material.toString();
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
if (filter.matches(material))
entry.selected = true;
materials_column.add(entry);
@ -147,7 +143,7 @@ private:
if (!selected_category.whole || material.matches(selected_category))
{
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
if (filter.matches(material))
entry.selected = true;
materials_column.add(entry);
@ -167,9 +163,10 @@ private:
}
};
DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; }
const DFHack::MaterialInfo &material_info_identity_fn(const DFHack::MaterialInfo &m) { return m; }
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter)
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter &filter)
: filter(filter)
{
selected_column = 0;
masks_column.setTitle("Type");
@ -179,7 +176,6 @@ ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter)
materials_column.left_margin = MAX_MASK + 3;
materials_column.setTitle("Material");
materials_column.multiselect = true;
this->filter = filter;
masks_column.changeHighlight(0);
@ -217,7 +213,7 @@ void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
}
if (input->count(interface_key::CUSTOM_SHIFT_C))
{
filter->clear();
filter.clear();
masks_column.clearSelection();
materials_column.clearSelection();
populateMaterials();
@ -225,17 +221,18 @@ void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
else if (input->count(interface_key::SEC_SELECT))
{
// Convert list selections to material filters
filter->mat_mask.whole = 0;
filter->materials.clear();
filter.clearMaterialMask();
// Category masks
auto masks = masks_column.getSelectedElems();
for (auto it = masks.begin(); it != masks.end(); ++it)
filter->mat_mask.whole |= it->whole;
filter.addMaterialMask(it->whole);
// Specific materials
auto materials = materials_column.getSelectedElems();
transform_(materials, filter->materials, material_info_identity_fn);
std::vector<DFHack::MaterialInfo> materialInfos;
transform_(materials, materialInfos, material_info_identity_fn);
filter.setMaterials(materialInfos);
Screen::dismiss(this);
}
@ -285,179 +282,167 @@ void ViewscreenChooseMaterial::render()
}
//START Viewscreen Hook
static bool is_planmode_enabled(df::building_type type)
static bool is_planmode_enabled(BuildingTypeKey key)
{
return planmode_enabled[type] || quickfort_mode;
return planmode_enabled[key] || quickfort_mode;
}
struct buildingplan_hook : public df::viewscreen_dwarfmodest
struct buildingplan_query_hook : public df::viewscreen_dwarfmodest
{
//START UI Methods
typedef df::viewscreen_dwarfmodest interpose_base;
void send_key(const df::interface_key &key)
{
set< df::interface_key > keys;
keys.insert(key);
this->feed(&keys);
}
bool isInPlannedBuildingQueryMode()
{
return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding ||
ui->main.mode == df::ui_sidebar_mode::BuildingItems) &&
planner.getSelectedPlannedBuilding();
planner.getPlannedBuilding(world->selected_building);
}
bool isInPlannedBuildingPlacementMode()
bool handleInput(set<df::interface_key> *input)
{
return ui->main.mode == ui_sidebar_mode::Build &&
df::global::ui_build_selector &&
df::global::ui_build_selector->stage < 2 &&
planner.isPlannableBuilding(ui_build_selector->building_type);
}
if (!isInPlannedBuildingQueryMode())
return false;
std::vector<Units::NoblePosition> getNoblePositionOfSelectedBuildingOwner()
{
std::vector<Units::NoblePosition> np;
if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding ||
!world->selected_building ||
!world->selected_building->owner)
if (input->count(interface_key::SUSPENDBUILDING))
return true; // Don't unsuspend planned buildings
if (input->count(interface_key::DESTROYBUILDING))
{
return np;
// remove persistent data and allow the parent to handle the key
// so the building is removed
planner.getPlannedBuilding(world->selected_building)->remove();
}
switch (world->selected_building->getType())
{
case building_type::Bed:
case building_type::Chair:
case building_type::Table:
break;
default:
return np;
}
return false;
}
return getUniqueNoblePositions(world->selected_building->owner);
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handleInput(input))
INTERPOSE_NEXT(feed)(input);
}
bool isInNobleRoomQueryMode()
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
if (getNoblePositionOfSelectedBuildingOwner().size() > 0)
return canReserveRoom(world->selected_building);
else
return false;
INTERPOSE_NEXT(render)();
if (!isInPlannedBuildingQueryMode())
return;
// Hide suspend toggle option
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = 20;
Screen::Pen pen(' ', COLOR_BLACK);
Screen::fillRect(pen, x, y, dims.menu_x2, y);
// all current buildings only have one filter
auto & filter = planner.getPlannedBuilding(world->selected_building)->getFilters()[0];
y = 24;
OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin);
++y;
OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin);
OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin);
OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin);
OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin);
if (filter.getDecoratedOnly())
OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin);
OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin);
auto filters = filter.getMaterials();
for (auto it = filters.begin(); it != filters.end(); ++it)
OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin);
}
};
struct buildingplan_place_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
bool isInPlannedBuildingPlacementMode()
{
return ui->main.mode == ui_sidebar_mode::Build &&
df::global::ui_build_selector &&
df::global::ui_build_selector->stage < 2 &&
planner.isPlannableBuilding(toBuildingTypeKey(ui_build_selector));
}
bool handleInput(set<df::interface_key> *input)
{
if (isInPlannedBuildingPlacementMode())
if (!isInPlannedBuildingPlacementMode())
{
auto type = ui_build_selector->building_type;
if (input->count(interface_key::CUSTOM_SHIFT_P))
{
planmode_enabled[type] = !planmode_enabled[type];
if (!is_planmode_enabled(type))
{
Gui::refreshSidebar();
in_dummy_screen = false;
}
return true;
}
else if (input->count(interface_key::CUSTOM_P) ||
input->count(interface_key::CUSTOM_F) ||
input->count(interface_key::CUSTOM_D) ||
input->count(interface_key::CUSTOM_N))
{
show_help = true;
}
show_help = false;
return false;
}
if (is_planmode_enabled(type))
if (in_dummy_screen)
{
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)
|| input->count(interface_key::LEAVESCREEN))
{
if (quickfort_mode && in_dummy_screen)
{
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)
|| input->count(interface_key::LEAVESCREEN))
{
in_dummy_screen = false;
send_key(interface_key::LEAVESCREEN);
}
return true;
}
if (input->count(interface_key::SELECT))
{
if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type))
{
Gui::refreshSidebar();
if (quickfort_mode)
{
in_dummy_screen = true;
}
}
return true;
}
else if (input->count(interface_key::CUSTOM_SHIFT_F))
{
quickfort_mode = !quickfort_mode;
}
else if (input->count(interface_key::CUSTOM_SHIFT_M))
{
Screen::show(dts::make_unique<ViewscreenChooseMaterial>(planner.getDefaultItemFilterForType(type)), plugin_self);
}
else if (input->count(interface_key::CUSTOM_Q))
{
planner.adjustMinQuality(type, -1);
}
else if (input->count(interface_key::CUSTOM_W))
{
planner.adjustMinQuality(type, 1);
}
else if (input->count(interface_key::CUSTOM_SHIFT_Q))
{
planner.adjustMaxQuality(type, -1);
}
else if (input->count(interface_key::CUSTOM_SHIFT_W))
{
planner.adjustMaxQuality(type, 1);
}
else if (input->count(interface_key::CUSTOM_SHIFT_D))
{
planner.getDefaultItemFilterForType(type)->decorated_only =
!planner.getDefaultItemFilterForType(type)->decorated_only;
}
in_dummy_screen = false;
// pass LEAVESCREEN up to parent view
input->clear();
input->insert(interface_key::LEAVESCREEN);
return false;
}
return true;
}
else if (isInPlannedBuildingQueryMode())
if (input->count(interface_key::CUSTOM_P) ||
input->count(interface_key::CUSTOM_F) ||
input->count(interface_key::CUSTOM_D) ||
input->count(interface_key::CUSTOM_M))
{
if (input->count(interface_key::SUSPENDBUILDING))
{
return true; // Don't unsuspend planned buildings
}
else if (input->count(interface_key::DESTROYBUILDING))
{
planner.removeSelectedPlannedBuilding(); // Remove persistent data
}
show_help = true;
}
BuildingTypeKey key = toBuildingTypeKey(ui_build_selector);
if (input->count(interface_key::CUSTOM_SHIFT_P))
{
planmode_enabled[key] = !planmode_enabled[key];
if (!is_planmode_enabled(key))
Gui::refreshSidebar();
return true;
}
else if (isInNobleRoomQueryMode())
if (input->count(interface_key::CUSTOM_SHIFT_F))
{
if (Gui::inRenameBuilding())
return false;
auto np = getNoblePositionOfSelectedBuildingOwner();
df::interface_key last_token = get_string_key(input);
if (last_token >= interface_key::STRING_A048 && last_token <= interface_key::STRING_A058)
quickfort_mode = !quickfort_mode;
return true;
}
if (!is_planmode_enabled(key))
return false;
if (input->count(interface_key::SELECT))
{
if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(key))
{
size_t selection = last_token - interface_key::STRING_A048;
if (np.size() < selection)
return false;
roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code);
return true;
Gui::refreshSidebar();
if (quickfort_mode)
in_dummy_screen = true;
}
return true;
}
return false;
// all current buildings only have one filter
auto filter = planner.getItemFilters(key).rbegin();
if (input->count(interface_key::CUSTOM_SHIFT_M))
Screen::show(dts::make_unique<ViewscreenChooseMaterial>(*filter), plugin_self);
else if (input->count(interface_key::CUSTOM_Q))
filter->decMinQuality();
else if (input->count(interface_key::CUSTOM_W))
filter->incMinQuality();
else if (input->count(interface_key::CUSTOM_SHIFT_Q))
filter->decMaxQuality();
else if (input->count(interface_key::CUSTOM_SHIFT_W))
filter->incMaxQuality();
else if (input->count(interface_key::CUSTOM_SHIFT_D))
filter->toggleDecoratedOnly();
else
return false;
return true;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
@ -469,133 +454,176 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
bool plannable = isInPlannedBuildingPlacementMode();
if (plannable && is_planmode_enabled(ui_build_selector->building_type))
BuildingTypeKey key = toBuildingTypeKey(ui_build_selector);
if (plannable && is_planmode_enabled(key))
{
if (ui_build_selector->stage < 1)
{
// No materials but turn on cursor
ui_build_selector->stage = 1;
}
for (auto iter = ui_build_selector->errors.begin(); iter != ui_build_selector->errors.end();)
for (auto iter = ui_build_selector->errors.begin();
iter != ui_build_selector->errors.end();)
{
//FIXME Hide bags
if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") ||
(*iter)->find("No access") != string::npos)
{
// FIXME Hide bags
if (((*iter)->find("Needs") != string::npos
&& **iter != "Needs adjacent wall")
|| (*iter)->find("No access") != string::npos)
iter = ui_build_selector->errors.erase(iter);
}
else
{
++iter;
}
}
}
INTERPOSE_NEXT(render)();
if (!plannable)
return;
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
auto type = ui_build_selector->building_type;
if (plannable)
if (in_dummy_screen)
{
if (quickfort_mode && in_dummy_screen)
{
Screen::Pen pen(' ',COLOR_BLACK);
int y = dims.y1 + 1;
Screen::fillRect(pen, x, y, dims.menu_x2, y + 20);
Screen::Pen pen(' ',COLOR_BLACK);
int y = dims.y1 + 1;
Screen::fillRect(pen, x, y, dims.menu_x2, y + 20);
++y;
++y;
OutputString(COLOR_BROWN, x, y, "Quickfort Placeholder", true, left_margin);
OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin);
}
else
{
int y = 23;
OutputString(COLOR_BROWN, x, y,
"Placeholder for legacy Quickfort. This screen is not required for DFHack native quickfort.",
true, left_margin);
OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin);
return;
}
if (show_help)
{
OutputString(COLOR_BROWN, x, y, "Note: ");
OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin);
}
OutputToggleString(x, y, "Planning Mode", "P", is_planmode_enabled(type), true, left_margin);
int y = 23;
if (is_planmode_enabled(type))
{
OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin);
if (show_help)
{
OutputString(COLOR_BROWN, x, y, "Note: ");
OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin);
}
auto filter = planner.getDefaultItemFilterForType(type);
OutputToggleString(x, y, "Planning Mode", "P", planmode_enabled[key], true, left_margin);
OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin);
OutputHotkeyString(x, y, "Min Quality: ", "qw");
OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin);
if (!is_planmode_enabled(key))
return;
OutputHotkeyString(x, y, "Max Quality: ", "QW");
OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin);
auto filter = planner.getItemFilters(key).rbegin();
y += 2;
OutputHotkeyString(x, y, "Min Quality: ", "qw");
OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin);
OutputToggleString(x, y, "Decorated Only: ", "D", filter->decorated_only, true, left_margin);
OutputHotkeyString(x, y, "Max Quality: ", "QW");
OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin);
OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin);
auto filter_descriptions = filter->getMaterialFilterAsVector();
for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it)
OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin);
}
else
{
in_dummy_screen = false;
}
}
}
else if (isInPlannedBuildingQueryMode())
OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin);
OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin);
auto filter_descriptions = filter->getMaterials();
for (auto it = filter_descriptions.begin();
it != filter_descriptions.end(); ++it)
OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin);
}
};
struct buildingplan_room_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
std::vector<Units::NoblePosition> getNoblePositionOfSelectedBuildingOwner()
{
std::vector<Units::NoblePosition> np;
if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding ||
!world->selected_building ||
!world->selected_building->owner)
{
in_dummy_screen = false;
// Hide suspend toggle option
int y = 20;
Screen::Pen pen(' ', COLOR_BLACK);
Screen::fillRect(pen, x, y, dims.menu_x2, y);
auto filter = planner.getSelectedPlannedBuilding()->getFilter();
y = 24;
OutputString(COLOR_BROWN, x, y, "Planned Building Filter:", true, left_margin);
OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin);
OutputString(COLOR_BLUE, x, y, filter->getMinQuality(), true, left_margin);
OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin);
OutputString(COLOR_BLUE, x, y, filter->getMaxQuality(), true, left_margin);
if (filter->decorated_only)
OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin);
OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin);
auto filters = filter->getMaterialFilterAsVector();
for (auto it = filters.begin(); it != filters.end(); ++it)
OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin);
return np;
}
else if (isInNobleRoomQueryMode())
switch (world->selected_building->getType())
{
auto np = getNoblePositionOfSelectedBuildingOwner();
int y = 24;
OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin);
OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin);
for (size_t i = 0; i < np.size() && i < 9; i++)
{
bool enabled = (roomMonitor.getReservedNobleCode(world->selected_building->id)
== np[i].position->code);
OutputToggleString(x, y, np[i].position->name[0].c_str(),
int_to_string(i+1).c_str(), enabled, true, left_margin);
}
case building_type::Bed:
case building_type::Chair:
case building_type::Table:
break;
default:
return np;
}
return getUniqueNoblePositions(world->selected_building->owner);
}
bool isInNobleRoomQueryMode()
{
if (getNoblePositionOfSelectedBuildingOwner().size() > 0)
return canReserveRoom(world->selected_building);
else
return false;
}
bool handleInput(set<df::interface_key> *input)
{
if (!isInNobleRoomQueryMode())
return false;
if (Gui::inRenameBuilding())
return false;
auto np = getNoblePositionOfSelectedBuildingOwner();
df::interface_key last_token = get_string_key(input);
if (last_token >= interface_key::STRING_A048
&& last_token <= interface_key::STRING_A058)
{
in_dummy_screen = false;
show_help = false;
size_t selection = last_token - interface_key::STRING_A048;
if (np.size() < selection)
return false;
roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code);
return true;
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handleInput(input))
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (!isInNobleRoomQueryMode())
return;
auto np = getNoblePositionOfSelectedBuildingOwner();
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = 24;
OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin);
OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin);
for (size_t i = 0; i < np.size() && i < 9; i++)
{
bool enabled =
roomMonitor.getReservedNobleCode(world->selected_building->id)
== np[i].position->code;
OutputToggleString(x, y, np[i].position->name[0].c_str(),
int_to_string(i+1).c_str(), enabled, true, left_margin);
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render);
static command_result buildingplan_cmd(color_ostream &out, vector <string> & parameters)
{
@ -624,10 +652,14 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
if (enable != is_enabled)
{
planner.reset(out);
if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_hook, render).apply(enable))
planner.reset();
if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_room_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_query_hook, render).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_place_hook, render).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_room_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
@ -640,7 +672,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
{
commands.push_back(
PluginCommand(
"buildingplan", "Place furniture before it's built",
"buildingplan", "Plan building construction before you have materials",
buildingplan_cmd, false, "Run 'buildingplan debug [on|off]' to toggle debugging, or 'buildingplan version' to query the plugin version."));
planner.initialize();
@ -651,7 +683,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
{
switch (event) {
case SC_MAP_LOADED:
planner.reset(out);
planner.reset();
roomMonitor.reset(out);
break;
default:
@ -661,13 +693,17 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK;
}
static bool cycle_requested = false;
#define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &)
{
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0)
if (Maps::IsValid() && !World::ReadPauseState()
&& (cycle_requested || world->frame_counter % (DAY_TICKS/2) == 0))
{
planner.doCycle();
roomMonitor.doCycle();
cycle_requested = false;
}
return CR_OK;
@ -680,8 +716,11 @@ DFhackCExport command_result plugin_shutdown(color_ostream &)
// Lua API section
static bool isPlannableBuilding(df::building_type type) {
return planner.isPlannableBuilding(type);
static bool isPlannableBuilding(df::building_type type,
int16_t subtype,
int32_t custom) {
return planner.isPlannableBuilding(
toBuildingTypeKey(type, subtype, custom));
}
static void addPlannedBuilding(df::building *bld) {
@ -692,9 +731,14 @@ static void doCycle() {
planner.doCycle();
}
static void scheduleCycle() {
cycle_requested = true;
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(isPlannableBuilding),
DFHACK_LUA_FUNCTION(addPlannedBuilding),
DFHACK_LUA_FUNCTION(doCycle),
DFHACK_LUA_FUNCTION(scheduleCycle),
DFHACK_LUA_END
};

@ -48,7 +48,7 @@ struct BuildingInfo {
}
bool allocate() {
return planner.allocatePlannedBuilding(type);
return planner.allocatePlannedBuilding(toBuildingTypeKey(type, -1, -1));
}
};
@ -122,7 +122,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
if (enable != is_enabled)
{
planner.reset(out);
planner.reset();
is_enabled = enable;
}

@ -4,9 +4,10 @@ local _ENV = mkmodule('plugins.buildingplan')
Native functions:
* bool isPlannableBuilding(df::building_type type)
* bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)
* void addPlannedBuilding(df::building *bld)
* void doCycle()
* void scheduleCycle()
--]]

@ -79,7 +79,7 @@ static void for_each_(map<T, V> &v, Fn func)
}
template <class T, class V, typename Fn>
static void transform_(vector<T> &src, vector<V> &dst, Fn func)
static void transform_(const vector<T> &src, vector<V> &dst, Fn func)
{
transform(src.begin(), src.end(), back_inserter(dst), func);
}