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: 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 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 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 burrows
======= =======

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

@ -305,7 +305,7 @@ bool MaterialInfo::findProduct(df::material *material, const std::string &name)
return decode(-1); return decode(-1);
} }
std::string MaterialInfo::getToken() std::string MaterialInfo::getToken() const
{ {
if (isNone()) if (isNone())
return "NONE"; 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()) if (isNone())
return "any"; return "any";

@ -1,16 +1,22 @@
#include "buildingplan-lib.h" #include "buildingplan-lib.h"
#include <cstdarg>
#include "Core.h" #include "Core.h"
using namespace DFHack; using namespace DFHack;
bool show_debugging = false; bool show_debugging = false;
void debug(const std::string &msg) void debug(const char *fmt, ...)
{ {
if (!show_debugging) if (!show_debugging)
return; return;
color_ostream_proxy out(Core::getInstance().getConsole()); 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-planner.h"
#include "buildingplan-rooms.h" #include "buildingplan-rooms.h"
void debug(const std::string &msg); void debug(const char *fmt, ...) Wformat(printf,1,2);
extern bool show_debugging; 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/general_ref_building_holderst.h"
#include "df/job_item.h" #include "df/job_item.h"
#include "df/building_doorst.h" #include "df/ui_build_selector.h"
#include "df/building_design.h"
#include "modules/Job.h"
#include "modules/Buildings.h" #include "modules/Buildings.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Job.h"
#include "uicommon.h"
#include "buildingplan-planner.h" #include "buildingplan-planner.h"
#include "buildingplan-lib.h" #include "buildingplan-lib.h"
#include "uicommon.h"
/* static const std::string planned_building_persistence_key_v1 = "buildingplan/constraints";
* ItemFilter
*/
ItemFilter::ItemFilter() : /*
min_quality(df::item_quality::Ordinary), * ItemFilter
max_quality(df::item_quality::Artifact), */
decorated_only(false),
valid(true)
{
clear(); // mat_mask is not cleared by default (see issue #1047)
}
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) clear();
if (material.matches(*it))
return true;
return false;
}
bool ItemFilter::matches(df::item *item) std::vector<std::string> tokens;
{ split_string(&tokens, config.val(), "/");
if (item->getQuality() < min_quality || item->getQuality() > max_quality) if (tokens.size() != 2)
{
debug("invalid ItemFilter serialization: '%s'", config.val().c_str());
return false; return false;
}
if (decorated_only && !item->hasImprovements()) if (!deserializeMaterialMask(tokens[0]) || !deserializeMaterials(tokens[1]))
return false; return false;
auto imattype = item->getActualMaterial(); setMinQuality(config.ival(2) - 1);
auto imatindex = item->getActualMaterialIndex(); setMaxQuality(config.ival(4) - 1);
auto item_mat = DFHack::MaterialInfo(imattype, imatindex); decorated_only = config.ival(3) - 1;
return true;
return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat);
} }
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) void ItemFilter::serialize(PersistentDataItem &config) const
bitfield_to_string(&descriptions, mat_mask); {
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) void ItemFilter::clearMaterialMask()
descriptions.push_back("any"); {
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, ",")); static void clampItemQuality(df::item_quality *quality)
str.append("/"); {
if (materials.size() > 0) if (*quality > item_quality::Artifact)
{ {
for (size_t i = 0; i < materials.size(); i++) debug("clamping quality to Artifact");
str.append(materials[i].getToken() + ","); *quality = item_quality::Artifact;
if (str[str.size()-1] == ',')
str.resize(str.size () - 1);
} }
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; max_quality = static_cast<df::item_quality>(quality);
std::vector<std::string> tokens; clampItemQuality(&max_quality);
split_string(&tokens, str, "/"); if (max_quality < min_quality)
min_quality = max_quality;
}
if (tokens.size() > 0 && !tokens[0].empty()) void ItemFilter::incMinQuality() { setMinQuality(min_quality + 1); }
{ void ItemFilter::decMinQuality() { setMinQuality(min_quality - 1); }
if (!parseJobMaterialCategory(&mat_mask, tokens[0])) void ItemFilter::incMaxQuality() { setMaxQuality(max_quality + 1); }
return false; void ItemFilter::decMaxQuality() { setMaxQuality(max_quality - 1); }
}
if (tokens.size() > 1 && !tokens[1].empty()) void ItemFilter::toggleDecoratedOnly() { decorated_only = !decorated_only; }
{
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); static std::string material_to_string_fn(const MaterialInfo &m) { return m.toString(); }
}
}
valid = true; uint32_t ItemFilter::getMaterialMask() const { return mat_mask.whole; }
return true;
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); 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); 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; return mat_mask.whole ? mat.matches(mat_mask) : true;
materials.clear();
} }
/* bool ItemFilter::matches(df::dfhack_material_category mask) const
* PlannedBuilding {
*/ return mask.whole & mat_mask.whole;
}
PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter) bool ItemFilter::matches(DFHack::MaterialInfo &material) const
{ {
this->building = building; for (auto it = materials.begin(); it != materials.end(); ++it)
this->filter = *filter; if (material.matches(*it))
pos = df::coord(building->centerx, building->centery, building->z); return true;
config = DFHack::World::AddPersistentData("buildingplan/constraints"); return false;
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;
} }
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())) if (decorated_only && !item->hasImprovements())
{ return false;
out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str());
return;
}
building = df::building::find(config.ival(1)); auto imattype = item->getActualMaterial();
if (!building) auto imatindex = item->getActualMaterialIndex();
return; 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); static std::vector<ItemFilter> deserializeFilters(PersistentDataItem &config)
filter.min_quality = static_cast<df::item_quality>(config.ival(2) - 1); {
filter.max_quality = static_cast<df::item_quality>(config.ival(4) - 1); // simplified implementation while we can assume there is only one filter
filter.decorated_only = config.ival(3) - 1; 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) bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
{ {
decltype(items_vector->begin()) closest_item; 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++) for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++)
{ {
auto item = *item_iter; auto item = *item_iter;
if (!filter.matches(item)) if (!filters[0].matches(item))
continue; continue;
auto pos = item->pos; auto pos = item->pos;
@ -255,68 +339,83 @@ bool PlannedBuilding::assignItem(df::item *item)
return true; 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() && return building && df::building::find(building_id)
building && Buildings::findAtTile(pos) == building && && building->getBuildStage() == 0;
building->getBuildStage() == 0;
if (!valid)
remove();
return valid;
} }
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);
} }
/* BuildingTypeKey toBuildingTypeKey(df::building *bld)
* Planner {
*/ 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(); // cast first param to appease gcc-4.8, which is missing the enum
std::vector<PersistentDataItem> items; // specializations for std::hash
DFHack::World::GetPersistentData(&items, "buildingplan/constraints"); 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++) return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16);
{
PlannedBuilding pb(*i, out);
if (pb.isValid())
planned_buildings.push_back(pb);
}
} }
/*
* Planner
*/
void Planner::initialize() void Planner::initialize()
{ {
#define add_building_type(btype, itype) \ #define add_building_type(btype, itype) \
item_for_building_type[df::building_type::btype] = df::item_type::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 *>(); \ available_item_vectors[df::item_type::itype] = std::vector<df::item *>(); \
is_relevant_item_type[df::item_type::itype] = true; \ is_relevant_item_type[df::item_type::itype] = true; \
@ -354,53 +453,82 @@ void Planner::initialize()
#undef add_building_type #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"); auto item_filters = getItemFilters(toBuildingTypeKey(bld)).get();
if (planned_buildings.size() == 0) // not a supported type
if (item_filters.empty())
{
debug("failed to add building: unsupported type");
return; 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(); PlannedBuilding pb(bld, item_filters);
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();) if (pb.isValid())
{ {
if (building_iter->isValid()) for (auto job : bld->jobs)
{ job->flags.bits.suspend = true;
if (show_debugging)
debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType()));
auto required_item_type = item_for_building_type[building_iter->getType()]; planned_buildings.push_back(pb);
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);
} }
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; coord32_t cursor;
if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z))
return false; return false;
auto type = std::get<0>(key);
auto newinst = Buildings::allocInstance(cursor.get_coord16(), type); auto newinst = Buildings::allocInstance(cursor.get_coord16(), type);
if (!newinst) if (!newinst)
return false; return false;
@ -430,53 +558,48 @@ bool Planner::allocatePlannedBuilding(df::building_type type)
return true; 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++) static std::vector<ItemFilter> empty_vector;
{ static const ItemFiltersWrapper empty_ret(empty_vector);
if (building_iter->isCurrentlySelectedBuilding())
{
return &(*building_iter);
}
}
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(); } void Planner::doCycle()
ItemFilter *Planner::getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; }
void Planner::adjustMinQuality(df::building_type type, int amount)
{ {
auto min_quality = &getDefaultItemFilterForType(type)->min_quality; debug("Running Cycle");
*min_quality = static_cast<df::item_quality>(*min_quality + amount); if (planned_buildings.size() == 0)
return;
boundsCheckItemQuality(min_quality);
auto max_quality = &getDefaultItemFilterForType(type)->max_quality;
if (*min_quality > *max_quality)
(*max_quality) = *min_quality;
} debug("Planned count: %zu", planned_buildings.size());
void Planner::adjustMaxQuality(df::building_type type, int amount) gather_available_items();
{ for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
auto max_quality = &getDefaultItemFilterForType(type)->max_quality; {
*max_quality = static_cast<df::item_quality>(*max_quality + amount); if (building_iter->isValid())
{
boundsCheckItemQuality(max_quality); auto type = building_iter->getBuilding()->getType();
auto min_quality = &getDefaultItemFilterForType(type)->min_quality; debug("Trying to allocate %s", enum_item_key_str(type));
if (*max_quality < *min_quality)
(*min_quality) = *max_quality;
}
void Planner::boundsCheckItemQuality(item_quality::item_quality *quality) auto required_item_type = item_for_building_type[type];
{ auto items_vector = &available_item_vectors[required_item_type];
*quality = static_cast<df::item_quality>(*quality); if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector))
if (*quality > item_quality::Artifact) {
(*quality) = item_quality::Artifact; debug("Unable to allocate an item");
if (*quality < item_quality::Ordinary) ++building_iter;
(*quality) = item_quality::Ordinary; continue;
}
}
debug("Removing building plan");
building_iter->remove();
building_iter = planned_buildings.erase(building_iter);
}
} }
void Planner::gather_available_items() void Planner::gather_available_items()

@ -1,99 +1,131 @@
#pragma once #pragma once
#include "df/item_quality.h" #include <unordered_map>
#include "df/building.h"
#include "df/dfhack_material_category.h" #include "df/dfhack_material_category.h"
#include "df/item_quality.h"
#include "df/job_item.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "modules/Persistence.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; df::dfhack_material_category mat_mask;
std::vector<DFHack::MaterialInfo> materials; std::vector<DFHack::MaterialInfo> materials;
df::item_quality min_quality; df::item_quality min_quality;
df::item_quality max_quality; df::item_quality max_quality;
bool decorated_only; bool decorated_only;
ItemFilter(); bool deserializeMaterialMask(std::string ser);
bool deserializeMaterials(std::string ser);
bool matchesMask(DFHack::MaterialInfo &mat); void setMinQuality(int quality);
bool matches(const df::dfhack_material_category mask) const; void setMaxQuality(int quality);
bool matches(DFHack::MaterialInfo &material) const; bool matchesMask(DFHack::MaterialInfo &mat) 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;
}; };
class PlannedBuilding class PlannedBuilding
{ {
public: public:
PlannedBuilding(df::building *building, ItemFilter *filter); PlannedBuilding(df::building *building, const std::vector<ItemFilter> &filters);
PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out); PlannedBuilding(DFHack::PersistentDataItem &config);
bool assignClosestItem(std::vector<df::item *> *items_vector); bool assignClosestItem(std::vector<df::item *> *items_vector);
bool assignItem(df::item *item); bool assignItem(df::item *item);
bool isValid(); bool isValid() const;
void remove(); void remove();
df::building_type getType(); df::building * getBuilding();
bool isCurrentlySelectedBuilding(); const std::vector<ItemFilter> & getFilters() const;
ItemFilter *getFilter();
private: private:
df::building *building;
DFHack::PersistentDataItem config; DFHack::PersistentDataItem config;
df::coord pos; df::building *building;
ItemFilter filter; 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 class Planner
{ {
public: public:
Planner(); class ItemFiltersWrapper
{
bool isPlannableBuilding(const df::building_type type) const; public:
ItemFiltersWrapper(std::vector<ItemFilter> & item_filters)
void reset(DFHack::color_ostream &out); : 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 initialize();
void reset();
bool allocatePlannedBuilding(BuildingTypeKey key);
void addPlannedBuilding(df::building *bld); void addPlannedBuilding(df::building *bld);
PlannedBuilding *getPlannedBuilding(df::building *bld);
void doCycle(); bool isPlannableBuilding(BuildingTypeKey key);
bool allocatePlannedBuilding(df::building_type type);
PlannedBuilding *getSelectedPlannedBuilding();
void removeSelectedPlannedBuilding(); // returns an empty vector if the type is not supported
ItemFiltersWrapper getItemFilters(BuildingTypeKey key);
ItemFilter *getDefaultItemFilterForType(df::building_type type); void doCycle();
void adjustMinQuality(df::building_type type, int amount);
void adjustMaxQuality(df::building_type type, int amount);
private: private:
std::map<df::building_type, df::item_type> item_for_building_type; 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, 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::map<df::item_type, bool> is_relevant_item_type; //Needed for fast check when looping over all items
std::vector<PlannedBuilding> planned_buildings; std::vector<PlannedBuilding> planned_buildings;
void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality);
void gather_available_items(); void gather_available_items();
}; };

@ -1,5 +1,5 @@
#include "buildingplan-lib.h" #include <unordered_map>
#include "df/entity_position.h" #include "df/entity_position.h"
#include "df/interface_key.h" #include "df/interface_key.h"
#include "df/ui_build_selector.h" #include "df/ui_build_selector.h"
@ -14,6 +14,7 @@
#include "uicommon.h" #include "uicommon.h"
#include "listcolumn.h" #include "listcolumn.h"
#include "buildingplan-lib.h"
DFHACK_PLUGIN("buildingplan"); DFHACK_PLUGIN("buildingplan");
#define PLUGIN_VERSION 0.15 #define PLUGIN_VERSION 0.15
@ -24,18 +25,15 @@ REQUIRE_GLOBAL(world);
#define MAX_MASK 10 #define MAX_MASK 10
#define MAX_MATERIAL 21 #define MAX_MATERIAL 21
using namespace DFHack;
using namespace df::enums;
bool show_help = false; bool show_help = false;
bool quickfort_mode = false; bool quickfort_mode = false;
bool in_dummy_screen = 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 class ViewscreenChooseMaterial : public dfhack_viewscreen
{ {
public: public:
ViewscreenChooseMaterial(ItemFilter *filter); ViewscreenChooseMaterial(ItemFilter &filter);
void feed(set<df::interface_key> *input); void feed(set<df::interface_key> *input);
@ -47,14 +45,12 @@ private:
ListColumn<df::dfhack_material_category> masks_column; ListColumn<df::dfhack_material_category> masks_column;
ListColumn<MaterialInfo> materials_column; ListColumn<MaterialInfo> materials_column;
int selected_column; int selected_column;
ItemFilter *filter; ItemFilter &filter;
df::building_type btype;
void addMaskEntry(df::dfhack_material_category &mask, const std::string &text) 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); 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; entry.selected = true;
masks_column.add(entry); masks_column.add(entry);
@ -131,7 +127,7 @@ private:
material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i); material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i);
auto name = material.toString(); auto name = material.toString();
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material); ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material)) if (filter.matches(material))
entry.selected = true; entry.selected = true;
materials_column.add(entry); materials_column.add(entry);
@ -147,7 +143,7 @@ private:
if (!selected_category.whole || material.matches(selected_category)) if (!selected_category.whole || material.matches(selected_category))
{ {
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material); ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material)) if (filter.matches(material))
entry.selected = true; entry.selected = true;
materials_column.add(entry); 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; selected_column = 0;
masks_column.setTitle("Type"); masks_column.setTitle("Type");
@ -179,7 +176,6 @@ ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter)
materials_column.left_margin = MAX_MASK + 3; materials_column.left_margin = MAX_MASK + 3;
materials_column.setTitle("Material"); materials_column.setTitle("Material");
materials_column.multiselect = true; materials_column.multiselect = true;
this->filter = filter;
masks_column.changeHighlight(0); masks_column.changeHighlight(0);
@ -217,7 +213,7 @@ void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
} }
if (input->count(interface_key::CUSTOM_SHIFT_C)) if (input->count(interface_key::CUSTOM_SHIFT_C))
{ {
filter->clear(); filter.clear();
masks_column.clearSelection(); masks_column.clearSelection();
materials_column.clearSelection(); materials_column.clearSelection();
populateMaterials(); populateMaterials();
@ -225,17 +221,18 @@ void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
else if (input->count(interface_key::SEC_SELECT)) else if (input->count(interface_key::SEC_SELECT))
{ {
// Convert list selections to material filters // Convert list selections to material filters
filter->mat_mask.whole = 0; filter.clearMaterialMask();
filter->materials.clear();
// Category masks // Category masks
auto masks = masks_column.getSelectedElems(); auto masks = masks_column.getSelectedElems();
for (auto it = masks.begin(); it != masks.end(); ++it) for (auto it = masks.begin(); it != masks.end(); ++it)
filter->mat_mask.whole |= it->whole; filter.addMaterialMask(it->whole);
// Specific materials // Specific materials
auto materials = materials_column.getSelectedElems(); 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); Screen::dismiss(this);
} }
@ -285,179 +282,167 @@ void ViewscreenChooseMaterial::render()
} }
//START Viewscreen Hook //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; 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() bool isInPlannedBuildingQueryMode()
{ {
return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding || return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding ||
ui->main.mode == df::ui_sidebar_mode::BuildingItems) && 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 && if (!isInPlannedBuildingQueryMode())
df::global::ui_build_selector && return false;
df::global::ui_build_selector->stage < 2 &&
planner.isPlannableBuilding(ui_build_selector->building_type);
}
std::vector<Units::NoblePosition> getNoblePositionOfSelectedBuildingOwner() if (input->count(interface_key::SUSPENDBUILDING))
{ return true; // Don't unsuspend planned buildings
std::vector<Units::NoblePosition> np; if (input->count(interface_key::DESTROYBUILDING))
if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding ||
!world->selected_building ||
!world->selected_building->owner)
{ {
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()) return false;
{ }
case building_type::Bed:
case building_type::Chair:
case building_type::Table:
break;
default:
return np;
}
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) INTERPOSE_NEXT(render)();
return canReserveRoom(world->selected_building);
else if (!isInPlannedBuildingQueryMode())
return false; 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) bool handleInput(set<df::interface_key> *input)
{ {
if (isInPlannedBuildingPlacementMode()) if (!isInPlannedBuildingPlacementMode())
{ {
auto type = ui_build_selector->building_type; show_help = false;
if (input->count(interface_key::CUSTOM_SHIFT_P)) return false;
{ }
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;
}
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) in_dummy_screen = false;
{ // pass LEAVESCREEN up to parent view
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT) input->clear();
|| input->count(interface_key::LEAVESCREEN)) input->insert(interface_key::LEAVESCREEN);
{ return false;
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;
}
} }
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)) show_help = true;
{ }
return true; // Don't unsuspend planned buildings
}
else if (input->count(interface_key::DESTROYBUILDING))
{
planner.removeSelectedPlannedBuilding(); // Remove persistent data
}
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()) quickfort_mode = !quickfort_mode;
return false; return true;
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) 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; Gui::refreshSidebar();
if (np.size() < selection) if (quickfort_mode)
return false; in_dummy_screen = true;
roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code);
return 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)) 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, ()) DEFINE_VMETHOD_INTERPOSE(void, render, ())
{ {
bool plannable = isInPlannedBuildingPlacementMode(); 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) if (ui_build_selector->stage < 1)
{
// No materials but turn on cursor // No materials but turn on cursor
ui_build_selector->stage = 1; 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 // FIXME Hide bags
if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") || if (((*iter)->find("Needs") != string::npos
(*iter)->find("No access") != string::npos) && **iter != "Needs adjacent wall")
{ || (*iter)->find("No access") != string::npos)
iter = ui_build_selector->errors.erase(iter); iter = ui_build_selector->errors.erase(iter);
}
else else
{
++iter; ++iter;
}
} }
} }
INTERPOSE_NEXT(render)(); INTERPOSE_NEXT(render)();
if (!plannable)
return;
auto dims = Gui::getDwarfmodeViewDims(); auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1; int left_margin = dims.menu_x1 + 1;
int x = left_margin; 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::Pen pen(' ',COLOR_BLACK); Screen::fillRect(pen, x, y, dims.menu_x2, y + 20);
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_BROWN, x, y,
OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin); "Placeholder for legacy Quickfort. This screen is not required for DFHack native quickfort.",
} true, left_margin);
else OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin);
{ return;
int y = 23; }
if (show_help) int y = 23;
{
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);
if (is_planmode_enabled(type)) if (show_help)
{ {
OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin); 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"); if (!is_planmode_enabled(key))
OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin); return;
OutputHotkeyString(x, y, "Max Quality: ", "QW"); auto filter = planner.getItemFilters(key).rbegin();
OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin); 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); OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin);
auto filter_descriptions = filter->getMaterialFilterAsVector();
for (auto it = filter_descriptions.begin(); it != filter_descriptions.end(); ++it) OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin);
OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin); auto filter_descriptions = filter->getMaterials();
} for (auto it = filter_descriptions.begin();
else it != filter_descriptions.end(); ++it)
{ OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin);
in_dummy_screen = false; }
} };
}
} struct buildingplan_room_hook : public df::viewscreen_dwarfmodest
else if (isInPlannedBuildingQueryMode()) {
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; return np;
// 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);
} }
else if (isInNobleRoomQueryMode())
switch (world->selected_building->getType())
{ {
auto np = getNoblePositionOfSelectedBuildingOwner(); case building_type::Bed:
int y = 24; case building_type::Chair:
OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin); case building_type::Table:
OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin); break;
for (size_t i = 0; i < np.size() && i < 9; i++) default:
{ return np;
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);
}
} }
return getUniqueNoblePositions(world->selected_building->owner);
}
bool isInNobleRoomQueryMode()
{
if (getNoblePositionOfSelectedBuildingOwner().size() > 0)
return canReserveRoom(world->selected_building);
else 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; size_t selection = last_token - interface_key::STRING_A048;
show_help = false; 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_query_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); 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) 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) if (enable != is_enabled)
{ {
planner.reset(out); planner.reset();
if (!INTERPOSE_HOOK(buildingplan_hook, feed).apply(enable) || if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(buildingplan_hook, render).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; return CR_FAILURE;
is_enabled = enable; is_enabled = enable;
@ -640,7 +672,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
{ {
commands.push_back( commands.push_back(
PluginCommand( 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.")); buildingplan_cmd, false, "Run 'buildingplan debug [on|off]' to toggle debugging, or 'buildingplan version' to query the plugin version."));
planner.initialize(); planner.initialize();
@ -651,7 +683,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
{ {
switch (event) { switch (event) {
case SC_MAP_LOADED: case SC_MAP_LOADED:
planner.reset(out); planner.reset();
roomMonitor.reset(out); roomMonitor.reset(out);
break; break;
default: default:
@ -661,13 +693,17 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK; return CR_OK;
} }
static bool cycle_requested = false;
#define DAY_TICKS 1200 #define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &) 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(); planner.doCycle();
roomMonitor.doCycle(); roomMonitor.doCycle();
cycle_requested = false;
} }
return CR_OK; return CR_OK;
@ -680,8 +716,11 @@ DFhackCExport command_result plugin_shutdown(color_ostream &)
// Lua API section // Lua API section
static bool isPlannableBuilding(df::building_type type) { static bool isPlannableBuilding(df::building_type type,
return planner.isPlannableBuilding(type); int16_t subtype,
int32_t custom) {
return planner.isPlannableBuilding(
toBuildingTypeKey(type, subtype, custom));
} }
static void addPlannedBuilding(df::building *bld) { static void addPlannedBuilding(df::building *bld) {
@ -692,9 +731,14 @@ static void doCycle() {
planner.doCycle(); planner.doCycle();
} }
static void scheduleCycle() {
cycle_requested = true;
}
DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(isPlannableBuilding), DFHACK_LUA_FUNCTION(isPlannableBuilding),
DFHACK_LUA_FUNCTION(addPlannedBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding),
DFHACK_LUA_FUNCTION(doCycle), DFHACK_LUA_FUNCTION(doCycle),
DFHACK_LUA_FUNCTION(scheduleCycle),
DFHACK_LUA_END DFHACK_LUA_END
}; };

@ -48,7 +48,7 @@ struct BuildingInfo {
} }
bool allocate() { 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) if (enable != is_enabled)
{ {
planner.reset(out); planner.reset();
is_enabled = enable; is_enabled = enable;
} }

@ -4,9 +4,10 @@ local _ENV = mkmodule('plugins.buildingplan')
Native functions: 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 addPlannedBuilding(df::building *bld)
* void doCycle() * 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> 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); transform(src.begin(), src.end(), back_inserter(dst), func);
} }