prep buildingplan for core algorithm changes

player-visible changes
- removed text that showed up if you used the wrong hotkeys. no other
  dfhack screen does this, and it seems unneeded. can add back if others
  think otherwise, though

internal changes
- changed signature of lua-exported isPlannableBuilding to take subtype
  and custom type in addition to building type. this is only used by
  quickfort, and it already sends all three params in preparation for
  this change
- added lua-exported scheduleCycle(), which is like doCycle(), but only
  takes effect on the next non-paused frame. this lets quickfort
  run only one buildingplan cycle regardless of how many #build
  blueprints were run
- declared a few dfhack library methods and params const so buildingplan
  could call them from const methods
- converted buildingplan internal debug logging fn to have a printf api
- reshaped buildingplan-planner API and refactored implementation in
  preparation for upcoming core algorithm changes for supporing all
  building types (no externally-visible functionality changes)
  - changed df::building_type params to type, subtype, custom tuple keys
  - introduced capability to return multiple filters per building type
    (though the current buildings all only have one filter per)
- split monolith hook functions in buildingplan.cpp into one per scope.
  this significantly cleans up the code and preps the hooks to handle
  iterating through multiple item filters.
- got rid of send_key function and replaced with better reporting of
  whether keys have been handled
develop
Myk Taylor 2020-10-04 20:05:08 -07:00
parent d9d949096e
commit 4d7f4d80ad
11 changed files with 704 additions and 530 deletions

@ -3691,9 +3691,10 @@ buildingplan
Native functions:
* ``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,68 +1,167 @@
#include "df/building_design.h"
#include "df/building_doorst.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"
static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints";
/*
* ItemFilter
*/
* ItemFilter
*/
ItemFilter::ItemFilter() :
min_quality(df::item_quality::Ordinary),
max_quality(df::item_quality::Artifact),
decorated_only(false),
valid(true)
ItemFilter::ItemFilter()
{
clear(); // mat_mask is not cleared by default (see issue #1047)
clear();
}
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat)
void ItemFilter::clear()
{
return (mat_mask.whole) ? mat.matches(mat_mask) : true;
min_quality = df::item_quality::Ordinary;
max_quality = df::item_quality::Masterful;
decorated_only = false;
clearMaterialMask();
materials.clear();
}
bool ItemFilter::matches(const df::dfhack_material_category mask) const
bool ItemFilter::deserialize(PersistentDataItem &config)
{
return mask.whole & mat_mask.whole;
clear();
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 (!deserializeMaterialMask(tokens[0]) || !deserializeMaterials(tokens[1]))
return false;
setMinQuality(config.ival(2) - 1);
setMaxQuality(config.ival(4) - 1);
decorated_only = config.ival(3) - 1;
return true;
}
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
bool ItemFilter::deserializeMaterialMask(std::string ser)
{
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
if (ser.empty())
return true;
if (!parseJobMaterialCategory(&mat_mask, ser))
{
debug("invalid job material category serialization: '%s'", ser.c_str());
return false;
}
return true;
}
bool ItemFilter::matches(df::item *item)
bool ItemFilter::deserializeMaterials(std::string ser)
{
if (item->getQuality() < min_quality || item->getQuality() > max_quality)
return false;
if (ser.empty())
return true;
if (decorated_only && !item->hasImprovements())
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;
}
auto imattype = item->getActualMaterial();
auto imatindex = item->getActualMaterialIndex();
auto item_mat = DFHack::MaterialInfo(imattype, imatindex);
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;
}
return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat);
void ItemFilter::clearMaterialMask()
{
mat_mask.whole = 0;
}
std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); }
void ItemFilter::addMaterialMask(uint32_t mask)
{
mat_mask.whole |= mask;
}
std::vector<std::string> ItemFilter::getMaterialFilterAsVector()
void ItemFilter::setMaterials(std::vector<DFHack::MaterialInfo> materials)
{
std::vector<std::string> descriptions;
this->materials = materials;
}
static void clampItemQuality(df::item_quality *quality)
{
if (*quality > item_quality::Artifact)
{
debug("clamping quality to Artifact");
*quality = item_quality::Artifact;
}
if (*quality < item_quality::Ordinary)
{
debug("clamping quality to Ordinary");
*quality = item_quality::Ordinary;
}
}
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;
}
void ItemFilter::setMaxQuality(int quality)
{
max_quality = static_cast<df::item_quality>(quality);
clampItemQuality(&max_quality);
if (max_quality < min_quality)
min_quality = max_quality;
}
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); }
void ItemFilter::toggleDecoratedOnly() { decorated_only = !decorated_only; }
static std::string material_to_string_fn(const MaterialInfo &m) { return m.toString(); }
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)
@ -74,112 +173,93 @@ std::vector<std::string> ItemFilter::getMaterialFilterAsVector()
return descriptions;
}
std::string ItemFilter::getMaterialFilterAsSerial()
std::string ItemFilter::getMinQuality() const
{
std::string str;
str.append(bitfield_to_string(mat_mask, ","));
str.append("/");
if (materials.size() > 0)
{
for (size_t i = 0; i < materials.size(); i++)
str.append(materials[i].getToken() + ",");
if (str[str.size()-1] == ',')
str.resize(str.size () - 1);
}
return str;
return ENUM_KEY_STR(item_quality, min_quality);
}
bool ItemFilter::parseSerializedMaterialTokens(std::string str)
std::string ItemFilter::getMaxQuality() const
{
valid = false;
std::vector<std::string> tokens;
split_string(&tokens, str, "/");
if (tokens.size() > 0 && !tokens[0].empty())
{
if (!parseJobMaterialCategory(&mat_mask, tokens[0]))
return false;
}
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;
materials.push_back(material);
}
}
return ENUM_KEY_STR(item_quality, max_quality);
}
valid = true;
return true;
bool ItemFilter::getDecoratedOnly() const
{
return decorated_only;
}
std::string ItemFilter::getMinQuality()
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat) const
{
return ENUM_KEY_STR(item_quality, min_quality);
return mat_mask.whole ? mat.matches(mat_mask) : true;
}
std::string ItemFilter::getMaxQuality()
bool ItemFilter::matches(df::dfhack_material_category mask) const
{
return ENUM_KEY_STR(item_quality, max_quality);
return mask.whole & mat_mask.whole;
}
bool ItemFilter::isValid()
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
{
return valid;
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
void ItemFilter::clear()
bool ItemFilter::matches(df::item *item) const
{
mat_mask.whole = 0;
materials.clear();
if (item->getQuality() < min_quality || item->getQuality() > max_quality)
return false;
if (decorated_only && !item->hasImprovements())
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);
}
/*
* PlannedBuilding
*/
* PlannedBuilding
*/
PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter)
static std::vector<ItemFilter> deserializeFilters(PersistentDataItem &config)
{
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;
// 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;
}
PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out)
static int getNumFilters(BuildingTypeKey key)
{
this->config = config;
if (!filter.parseSerializedMaterialTokens(config.val()))
{
out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str());
return;
}
building = df::building::find(config.ival(1));
if (!building)
return;
// TODO: get num filters in Lua when we handle all building types
return 1;
}
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;
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_id(config.ival(1)),
building(df::building::find(config.ival(1))),
filters(deserializeFilters(config))
{ }
bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
{
decltype(items_vector->begin()) closest_item;
@ -187,7 +267,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 +335,70 @@ 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()
{
DFHack::World::DeletePersistentData(config);
}
/*
* Planner
*/
* BuildingTypeKey
*/
Planner::Planner() { }
BuildingTypeKey toBuildingTypeKey(
df::building_type btype, int16_t subtype, int32_t custom)
{
return std::make_tuple(btype, subtype, custom);
}
bool Planner::isPlannableBuilding(const df::building_type type) const
BuildingTypeKey toBuildingTypeKey(df::building *bld)
{
return item_for_building_type.find(type) != item_for_building_type.end();
return std::make_tuple(
bld->getType(), bld->getSubtype(), bld->getCustomType());
}
void Planner::reset(color_ostream &out)
BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs)
{
planned_buildings.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/constraints");
return std::make_tuple(
uibs->building_type, uibs->building_subtype, uibs->custom_type);
}
for (auto i = items.begin(); i != items.end(); i++)
{
PlannedBuilding pb(*i, out);
if (pb.isValid())
planned_buildings.push_back(pb);
}
std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const
{
return std::hash<df::building_type>()(std::get<0>(key))
^ std::hash<int16_t>()(std::get<1>(key))
^ std::hash<int32_t>()(std::get<2>(key));
}
/*
* 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 +436,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");
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);
}
}
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()));
gather_available_items();
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
{
if (building_iter->isValid())
// protect against multiple registrations
if (getPlannedBuilding(bld))
{
if (show_debugging)
debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType()));
debug("building already registered");
return;
}
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))
PlannedBuilding pb(bld, item_filters);
if (pb.isValid())
{
debug("Unable to allocate an item");
++building_iter;
continue;
for (auto job : bld->jobs)
job->flags.bits.suspend = true;
planned_buildings.push_back(pb);
}
else
{
pb.remove();
}
debug("Removing building plan");
building_iter = planned_buildings.erase(building_iter);
}
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 +541,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;
int nfilters = getNumFilters(key);
if (nfilters < 1)
return empty_ret;
std::vector<ItemFilter> ret;
ret.push_back(item_filters[std::get<0>(key)]);
return ItemFiltersWrapper(ret);
}
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,129 @@
#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::map<df::building_type, ItemFilter> 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,4 +1,4 @@
#include "buildingplan-lib.h"
#include <unordered_map>
#include "df/entity_position.h"
#include "df/interface_key.h"
@ -14,6 +14,7 @@
#include "uicommon.h"
#include "listcolumn.h"
#include "buildingplan-lib.h"
DFHACK_PLUGIN("buildingplan");
#define PLUGIN_VERSION 0.15
@ -27,15 +28,14 @@ REQUIRE_GLOBAL(world);
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 +47,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 +129,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 +145,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 +165,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 +178,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 +215,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 +223,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,180 +284,158 @@ 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
planner.getPlannedBuilding(world->selected_building)->remove();
// still allow the building to be removed
return false;
}
switch (world->selected_building->getType())
return true;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
case building_type::Bed:
case building_type::Chair:
case building_type::Table:
break;
default:
return np;
if (!handleInput(input))
INTERPOSE_NEXT(feed)(input);
}
return getUniqueNoblePositions(world->selected_building->owner);
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
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);
}
};
bool isInNobleRoomQueryMode()
struct buildingplan_place_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
bool isInPlannedBuildingPlacementMode()
{
if (getNoblePositionOfSelectedBuildingOwner().size() > 0)
return canReserveRoom(world->selected_building);
else
return false;
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())
{
auto type = ui_build_selector->building_type;
if (input->count(interface_key::CUSTOM_SHIFT_P))
if (!isInPlannedBuildingPlacementMode())
return false;
if (in_dummy_screen)
{
planmode_enabled[type] = !planmode_enabled[type];
if (!is_planmode_enabled(type))
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)
|| input->count(interface_key::LEAVESCREEN))
{
Gui::refreshSidebar();
in_dummy_screen = false;
// pass LEAVESCREEN up to parent view
input->clear();
input->insert(interface_key::LEAVESCREEN);
return 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;
}
if (is_planmode_enabled(type))
{
if (quickfort_mode && in_dummy_screen)
{
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)
|| input->count(interface_key::LEAVESCREEN))
BuildingTypeKey key = toBuildingTypeKey(ui_build_selector);
if (input->count(interface_key::CUSTOM_SHIFT_P))
{
in_dummy_screen = false;
send_key(interface_key::LEAVESCREEN);
planmode_enabled[key] = !planmode_enabled[key];
if (!is_planmode_enabled(key))
Gui::refreshSidebar();
return true;
}
if (input->count(interface_key::CUSTOM_SHIFT_F))
{
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(type))
if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(key))
{
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);
}
// 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))
{
planner.adjustMinQuality(type, -1);
}
filter->decMinQuality();
else if (input->count(interface_key::CUSTOM_W))
{
planner.adjustMinQuality(type, 1);
}
filter->incMinQuality();
else if (input->count(interface_key::CUSTOM_SHIFT_Q))
{
planner.adjustMaxQuality(type, -1);
}
filter->decMaxQuality();
else if (input->count(interface_key::CUSTOM_SHIFT_W))
{
planner.adjustMaxQuality(type, 1);
}
filter->incMaxQuality();
else if (input->count(interface_key::CUSTOM_SHIFT_D))
{
planner.getDefaultItemFilterForType(type)->decorated_only =
!planner.getDefaultItemFilterForType(type)->decorated_only;
}
}
}
else if (isInPlannedBuildingQueryMode())
{
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
}
}
else if (isInNobleRoomQueryMode())
{
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)
{
size_t selection = last_token - interface_key::STRING_A048;
if (np.size() < selection)
filter->toggleDecoratedOnly();
else
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))
{
@ -469,38 +446,36 @@ 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();)
{
//FIXME Hide bags
if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") ||
(*iter)->find("No access") != string::npos)
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)
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 (quickfort_mode && in_dummy_screen)
if (in_dummy_screen)
{
Screen::Pen pen(' ',COLOR_BLACK);
int y = dims.y1 + 1;
@ -508,94 +483,133 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
++y;
OutputString(COLOR_BROWN, x, y, "Quickfort Placeholder", true, left_margin);
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;
}
else
{
int y = 23;
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, "Planning Mode", "P", planmode_enabled[key], true, left_margin);
OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin);
auto filter = planner.getDefaultItemFilterForType(type);
if (!is_planmode_enabled(key))
return;
auto filter = planner.getItemFilters(key).rbegin();
y += 2;
OutputHotkeyString(x, y, "Min Quality: ", "qw");
OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin);
OutputHotkeyString(x, y, "Max Quality: ", "QW");
OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin);
OutputToggleString(x, y, "Decorated Only: ", "D", filter->decorated_only, true, left_margin);
OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), 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)
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);
}
else
};
struct buildingplan_room_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
std::vector<Units::NoblePosition> getNoblePositionOfSelectedBuildingOwner()
{
in_dummy_screen = false;
std::vector<Units::NoblePosition> np;
if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding ||
!world->selected_building ||
!world->selected_building->owner)
{
return np;
}
switch (world->selected_building->getType())
{
case building_type::Bed:
case building_type::Chair:
case building_type::Table:
break;
default:
return np;
}
return getUniqueNoblePositions(world->selected_building->owner);
}
else if (isInPlannedBuildingQueryMode())
bool isInNobleRoomQueryMode()
{
in_dummy_screen = false;
if (getNoblePositionOfSelectedBuildingOwner().size() > 0)
return canReserveRoom(world->selected_building);
else
return false;
}
// Hide suspend toggle option
int y = 20;
Screen::Pen pen(' ', COLOR_BLACK);
Screen::fillRect(pen, x, y, dims.menu_x2, y);
bool handleInput(set<df::interface_key> *input)
{
if (!isInNobleRoomQueryMode())
return false;
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 (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)
{
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;
}
if (filter->decorated_only)
OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin);
return false;
}
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);
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handleInput(input))
INTERPOSE_NEXT(feed)(input);
}
else if (isInNobleRoomQueryMode())
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);
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);
}
}
else
{
in_dummy_screen = false;
show_help = false;
}
}
};
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 +638,14 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
if (enable != is_enabled)
{
planner.reset(out);
planner.reset();
if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_hook, render).apply(enable))
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 +658,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 +669,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 +679,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 +702,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 +717,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);
}