reorganize buildingplan code - no logic changes

- no API or logic changes, just moving code around
- split buildingplan-lib into planner and rooms files
- move business logic from .h files to .cpp files
develop
Myk Taylor 2020-09-08 00:17:56 -07:00
parent 8728056674
commit f3da131db7
9 changed files with 1257 additions and 1209 deletions

@ -74,7 +74,7 @@ add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_TMP_FILES})
set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
add_library(buildingplan-lib STATIC buildingplan-lib.cpp) add_library(buildingplan-lib STATIC buildingplan-lib.cpp buildingplan-planner.cpp buildingplan-rooms.cpp)
target_link_libraries(buildingplan-lib dfhack) target_link_libraries(buildingplan-lib dfhack)
# xlsxreader deps # xlsxreader deps

@ -1,690 +1,16 @@
#include "buildingplan-lib.h" #include "buildingplan-lib.h"
#define PLUGIN_VERSION 0.00 #include "Core.h"
void debug(const string &msg)
{
if (!show_debugging)
return;
color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl;
}
void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; }
/*
* Material Choice Screen
*/
std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); }
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat)
{
return (mat_mask.whole) ? mat.matches(mat_mask) : true;
}
bool ItemFilter::matches(const df::dfhack_material_category mask) const
{
return mask.whole & mat_mask.whole;
}
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
{
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
bool ItemFilter::matches(df::item *item)
{
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);
}
std::vector<std::string> ItemFilter::getMaterialFilterAsVector()
{
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; using namespace DFHack;
}
std::string ItemFilter::getMaterialFilterAsSerial()
{
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;
}
bool ItemFilter::parseSerializedMaterialTokens(std::string str)
{
valid = false;
std::vector<std::string> tokens;
split_string(&tokens, str, "/");
if (tokens.size() > 0 && !tokens[0].empty()) bool show_debugging = false;
{
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);
}
}
valid = true;
return true;
}
std::string ItemFilter::getMinQuality()
{
return ENUM_KEY_STR(item_quality, min_quality);
}
std::string ItemFilter::getMaxQuality()
{
return ENUM_KEY_STR(item_quality, max_quality);
}
bool ItemFilter::isValid()
{
return valid;
}
void ItemFilter::clear()
{
mat_mask.whole = 0;
materials.clear();
}
DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; }
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter)
{
selected_column = 0;
masks_column.setTitle("Type");
masks_column.multiselect = true;
masks_column.allow_search = false;
masks_column.left_margin = 2;
materials_column.left_margin = MAX_MASK + 3;
materials_column.setTitle("Material");
materials_column.multiselect = true;
this->filter = filter;
masks_column.changeHighlight(0);
populateMasks();
populateMaterials();
masks_column.selectDefaultEntry();
materials_column.selectDefaultEntry();
materials_column.changeHighlight(0);
}
void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
{
bool key_processed = false;
switch (selected_column)
{
case 0:
key_processed = masks_column.feed(input);
if (input->count(interface_key::SELECT))
populateMaterials(); // Redo materials lists based on category selection
break;
case 1:
key_processed = materials_column.feed(input);
break;
}
if (key_processed)
return;
if (input->count(interface_key::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
if (input->count(interface_key::CUSTOM_SHIFT_C))
{
filter->clear();
masks_column.clearSelection();
materials_column.clearSelection();
populateMaterials();
}
else if (input->count(interface_key::SEC_SELECT))
{
// Convert list selections to material filters
filter->mat_mask.whole = 0;
filter->materials.clear();
// Category masks
auto masks = masks_column.getSelectedElems();
for (auto it = masks.begin(); it != masks.end(); ++it)
filter->mat_mask.whole |= it->whole;
// Specific materials
auto materials = materials_column.getSelectedElems();
transform_(materials, filter->materials, material_info_identity_fn);
Screen::dismiss(this);
}
else if (input->count(interface_key::CURSOR_LEFT))
{
--selected_column;
validateColumn();
}
else if (input->count(interface_key::CURSOR_RIGHT))
{
selected_column++;
validateColumn();
}
else if (enabler->tracking_on && enabler->mouse_lbut)
{
if (masks_column.setHighlightByMouse())
selected_column = 0;
else if (materials_column.setHighlightByMouse())
selected_column = 1;
enabler->mouse_lbut = enabler->mouse_rbut = 0;
}
}
void ViewscreenChooseMaterial::render()
{
if (Screen::isDismissed(this))
return;
dfhack_viewscreen::render();
Screen::clear();
Screen::drawBorder(" Building Material ");
masks_column.display(selected_column == 0);
materials_column.display(selected_column == 1);
int32_t y = gps->dimy - 3;
int32_t x = 2;
OutputHotkeyString(x, y, "Toggle", interface_key::SELECT);
x += 3;
OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT);
x += 3;
OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C);
x += 3;
OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN);
}
// START Room Reservation
ReservedRoom::ReservedRoom(df::building *building, std::string noble_code)
{
this->building = building;
config = DFHack::World::AddPersistentData("buildingplan/reservedroom");
config.val() = noble_code;
config.ival(1) = building->id;
pos = df::coord(building->centerx, building->centery, building->z);
}
ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &out)
{
this->config = config;
building = df::building::find(config.ival(1));
if (!building)
return;
pos = df::coord(building->centerx, building->centery, building->z);
}
bool ReservedRoom::checkRoomAssignment()
{
if (!isValid())
return false;
auto np = getOwnersNobleCode();
bool correctOwner = false;
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
correctOwner = true;
break;
}
}
if (correctOwner)
return true;
for (auto iter = world->units.active.begin(); iter != world->units.active.end(); iter++)
{
df::unit* unit = *iter;
if (!Units::isCitizen(unit))
continue;
if (!Units::isActive(unit))
continue;
np = getUniqueNoblePositions(unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
Buildings::setOwner(building, unit);
break;
}
}
}
return true;
}
std::string RoomMonitor::getReservedNobleCode(int32_t buildingId)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId == iter->getId())
return iter->getCode();
}
return "";
}
void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code)
{
bool found = false;
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId != iter->getId())
{
continue;
}
else
{
if (noble_code == iter->getCode())
{
iter->remove();
reservedRooms.erase(iter);
}
else
{
iter->setCode(noble_code);
}
found = true;
break;
}
}
if (!found)
{
ReservedRoom room(df::building::find(buildingId), noble_code);
reservedRooms.push_back(room);
}
}
void RoomMonitor::doCycle()
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();)
{
if (iter->checkRoomAssignment())
{
++iter;
}
else
{
iter->remove();
iter = reservedRooms.erase(iter);
}
}
}
void RoomMonitor::reset(color_ostream &out)
{
reservedRooms.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom");
for (auto i = items.begin(); i != items.end(); i++)
{
ReservedRoom rr(*i, out);
if (rr.isValid())
addRoom(rr);
}
}
void delete_item_fn(df::job_item *x) { delete x; }
// START Planning
PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter)
{
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;
}
PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out)
{
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;
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;
}
bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
{
decltype(items_vector->begin()) closest_item;
int32_t closest_distance = -1;
for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++)
{
auto item = *item_iter;
if (!filter.matches(item))
continue;
auto pos = item->pos;
auto distance = abs(pos.x - building->centerx) +
abs(pos.y - building->centery) +
abs(pos.z - building->z) * 50;
if (closest_distance > -1 && distance >= closest_distance)
continue;
closest_distance = distance;
closest_item = item_iter;
}
if (closest_distance > -1 && assignItem(*closest_item))
{
debug("Item assigned");
items_vector->erase(closest_item);
remove();
return true;
}
return false;
}
bool PlannedBuilding::assignItem(df::item *item)
{
auto ref = df::allocate<df::general_ref_building_holderst>();
if (!ref)
{
Core::printerr("Could not allocate general_ref_building_holderst\n");
return false;
}
ref->building_id = building->id;
if (building->jobs.size() != 1)
return false;
auto job = building->jobs[0];
for_each_(job->job_items, delete_item_fn);
job->job_items.clear();
job->flags.bits.suspend = false;
bool rough = false;
Job::attachJobItem(job, item, df::job_item_ref::Hauled);
if (item->getType() == item_type::BOULDER)
rough = true;
building->mat_type = item->getMaterial();
building->mat_index = item->getMaterialIndex();
job->mat_type = building->mat_type;
job->mat_index = building->mat_index;
if (building->needsDesign())
{
auto act = (df::building_actual *) building;
act->design = new df::building_design();
act->design->flags.bits.rough = rough;
}
return true;
}
bool PlannedBuilding::isValid()
{
bool valid = filter.isValid() &&
building && Buildings::findAtTile(pos) == building &&
building->getBuildStage() == 0;
if (!valid)
remove();
return valid;
}
void Planner::reset(color_ostream &out)
{
planned_buildings.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/constraints");
for (auto i = items.begin(); i != items.end(); i++)
{
PlannedBuilding pb(*i, out);
if (pb.isValid())
planned_buildings.push_back(pb);
}
}
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; \
if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \
planmode_enabled[df::building_type::btype] = false
FOR_ENUM_ITEMS(item_type, it)
is_relevant_item_type[it] = false;
add_building_type(Armorstand, ARMORSTAND);
add_building_type(Bed, BED);
add_building_type(Chair, CHAIR);
add_building_type(Coffin, COFFIN);
add_building_type(Door, DOOR);
add_building_type(Floodgate, FLOODGATE);
add_building_type(Hatch, HATCH_COVER);
add_building_type(GrateWall, GRATE);
add_building_type(GrateFloor, GRATE);
add_building_type(BarsVertical, BAR);
add_building_type(BarsFloor, BAR);
add_building_type(Cabinet, CABINET);
add_building_type(Box, BOX);
// skip kennels, farm plot
add_building_type(Weaponrack, WEAPONRACK);
add_building_type(Statue, STATUE);
add_building_type(Slab, SLAB);
add_building_type(Table, TABLE);
// skip roads ... furnaces
add_building_type(WindowGlass, WINDOW);
// skip gem window ... support
add_building_type(AnimalTrap, ANIMALTRAP);
add_building_type(Chain, CHAIN);
add_building_type(Cage, CAGE);
// skip archery target
add_building_type(TractionBench, TRACTION_BENCH);
// skip nest box, hive (tools)
#undef add_building_type
}
void Planner::doCycle() void debug(const std::string &msg)
{ {
debug("Running Cycle"); if (!show_debugging)
if (planned_buildings.size() == 0)
return; return;
debug("Planned count: " + int_to_string(planned_buildings.size())); color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG: " << msg << endl;
gather_available_items();
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
{
if (building_iter->isValid())
{
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()];
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);
}
}
bool Planner::allocatePlannedBuilding(df::building_type type)
{
coord32_t cursor;
if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z))
return false;
auto newinst = Buildings::allocInstance(cursor.get_coord16(), type);
if (!newinst)
return false;
df::job_item *filter = new df::job_item();
filter->item_type = item_type::NONE;
filter->mat_index = 0;
filter->flags2.bits.building_material = true;
std::vector<df::job_item*> filters;
filters.push_back(filter);
if (!Buildings::constructWithFilters(newinst, filters))
{
delete newinst;
return false;
}
if (type == building_type::Door)
{
auto door = virtual_cast<df::building_doorst>(newinst);
if (door)
door->door_flags.bits.pet_passable = true;
}
addPlannedBuilding(newinst);
return true;
}
PlannedBuilding *Planner::getSelectedPlannedBuilding()
{
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++)
{
if (building_iter->isCurrentlySelectedBuilding())
{
return &(*building_iter);
}
}
return nullptr;
}
void Planner::adjustMinQuality(df::building_type type, int amount)
{
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;
}
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;
}
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;
} }
map<df::building_type, bool> planmode_enabled, saved_planmodes;
bool show_debugging = false;
bool show_help = false;
Planner planner;
RoomMonitor roomMonitor;

@ -1,509 +1,8 @@
#ifndef BUILDINGPLAN_H #pragma once
#define BUILDINGPLAN_H
#include "uicommon.h" #include "buildingplan-planner.h"
#include "listcolumn.h" #include "buildingplan-rooms.h"
#include <functional>
// DF data structure definition headers
#include "DataDefs.h"
#include "Types.h"
#include "df/build_req_choice_genst.h"
#include "df/build_req_choice_specst.h"
#include "df/item.h"
#include "df/ui.h"
#include "df/ui_build_selector.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/items_other_id.h"
#include "df/job.h"
#include "df/world.h"
#include "df/building_constructionst.h"
#include "df/building_design.h"
#include "df/entity_position.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/Items.h"
#include "modules/Units.h"
#include "modules/Gui.h"
#include "TileTypes.h"
#include "df/job_item.h"
#include "df/dfhack_material_category.h"
#include "df/general_ref_building_holderst.h"
#include "modules/Job.h"
#include "df/building_design.h"
#include "df/buildings_other_id.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/building_doorst.h"
using df::global::ui;
using df::global::ui_build_selector;
using df::global::world;
struct MaterialDescriptor
{
df::item_type item_type;
int16_t item_subtype;
int16_t type;
int32_t index;
bool valid;
bool matches(const MaterialDescriptor &a) const
{
return a.valid && valid &&
a.type == type &&
a.index == index &&
a.item_type == item_type &&
a.item_subtype == item_subtype;
}
};
#define MAX_MASK 10
#define MAX_MATERIAL 21
#define SIDEBAR_WIDTH 30
static inline bool canReserveRoom(df::building *building)
{
if (!building)
return false;
if (building->jobs.size() > 0 && building->jobs[0]->job_type == job_type::DestroyBuilding)
return false;
return building->is_room;
}
static inline std::vector<Units::NoblePosition> getUniqueNoblePositions(df::unit *unit)
{
std::vector<Units::NoblePosition> np;
Units::getNoblePositions(&np, unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == "MILITIA_CAPTAIN")
{
np.erase(iter);
break;
}
}
return np;
}
void delete_item_fn(df::job_item *x);
MaterialInfo &material_info_identity_fn(MaterialInfo &m);
extern map<df::building_type, bool> planmode_enabled, saved_planmodes;
void enable_quickfort_fn(pair<const df::building_type, bool>& pair);
void debug(const std::string &msg); void debug(const std::string &msg);
std::string material_to_string_fn(MaterialInfo m);
extern bool show_debugging; extern bool show_debugging;
extern bool show_help;
struct ItemFilter
{
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() : min_quality(df::item_quality::Ordinary), max_quality(df::item_quality::Artifact), decorated_only(false), valid(true)
{
clear(); // mat_mask is not cleared by default (see issue #1047)
}
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;
};
class ViewscreenChooseMaterial : public dfhack_viewscreen
{
public:
ViewscreenChooseMaterial(ItemFilter *filter);
void feed(set<df::interface_key> *input);
void render();
std::string getFocusString() { return "buildingplan_choosemat"; }
private:
ListColumn<df::dfhack_material_category> masks_column;
ListColumn<MaterialInfo> materials_column;
int selected_column;
ItemFilter *filter;
df::building_type btype;
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))
entry.selected = true;
masks_column.add(entry);
}
void populateMasks()
{
masks_column.clear();
df::dfhack_material_category mask;
mask.whole = 0;
mask.bits.stone = true;
addMaskEntry(mask, "Stone");
mask.whole = 0;
mask.bits.wood = true;
addMaskEntry(mask, "Wood");
mask.whole = 0;
mask.bits.metal = true;
addMaskEntry(mask, "Metal");
mask.whole = 0;
mask.bits.soap = true;
addMaskEntry(mask, "Soap");
masks_column.filterDisplay();
}
void populateMaterials()
{
materials_column.clear();
df::dfhack_material_category selected_category;
std::vector<df::dfhack_material_category> selected_masks = masks_column.getSelectedElems();
if (selected_masks.size() == 1)
selected_category = selected_masks[0];
else if (selected_masks.size() > 1)
return;
df::world_raws &raws = world->raws;
for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++)
{
auto obj = raws.mat_table.builtin[i];
if (obj)
{
MaterialInfo material;
material.decode(i, -1);
addMaterialEntry(selected_category, material, material.toString());
}
}
for (size_t i = 0; i < raws.inorganics.size(); i++)
{
df::inorganic_raw *p = raws.inorganics[i];
MaterialInfo material;
material.decode(0, i);
addMaterialEntry(selected_category, material, material.toString());
}
decltype(selected_category) wood_flag;
wood_flag.bits.wood = true;
if (!selected_category.whole || selected_category.bits.wood)
{
for (size_t i = 0; i < raws.plants.all.size(); i++)
{
df::plant_raw *p = raws.plants.all[i];
for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++)
{
auto t = p->material[j];
if (p->material[j]->id != "WOOD")
continue;
MaterialInfo material;
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))
entry.selected = true;
materials_column.add(entry);
}
}
}
materials_column.sort();
}
void addMaterialEntry(df::dfhack_material_category &selected_category,
MaterialInfo &material, std::string name)
{
if (!selected_category.whole || material.matches(selected_category))
{
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
entry.selected = true;
materials_column.add(entry);
}
}
void validateColumn()
{
set_to_limit(selected_column, 1);
}
void resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
masks_column.resize();
materials_column.resize();
}
};
class ReservedRoom
{
public:
ReservedRoom(df::building *building, std::string noble_code);
ReservedRoom(PersistentDataItem &config, color_ostream &out);
bool checkRoomAssignment();
void remove() { DFHack::World::DeletePersistentData(config); }
bool isValid()
{
if (!building)
return false;
if (Buildings::findAtTile(pos) != building)
return false;
return canReserveRoom(building);
}
int32_t getId()
{
if (!isValid())
return 0;
return building->id;
}
std::string getCode() { return config.val(); }
void setCode(const std::string &noble_code) { config.val() = noble_code; }
private:
df::building *building;
PersistentDataItem config;
df::coord pos;
std::vector<Units::NoblePosition> getOwnersNobleCode()
{
if (!building->owner)
return std::vector<Units::NoblePosition> ();
return getUniqueNoblePositions(building->owner);
}
};
class RoomMonitor
{
public:
RoomMonitor() { }
std::string getReservedNobleCode(int32_t buildingId);
void toggleRoomForPosition(int32_t buildingId, std::string noble_code);
void doCycle();
void reset(color_ostream &out);
private:
std::vector<ReservedRoom> reservedRooms;
void addRoom(ReservedRoom &rr)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (iter->getId() == rr.getId())
return;
}
reservedRooms.push_back(rr);
}
};
// START Planning
class PlannedBuilding
{
public:
PlannedBuilding(df::building *building, ItemFilter *filter);
PlannedBuilding(PersistentDataItem &config, color_ostream &out);
bool assignClosestItem(std::vector<df::item *> *items_vector);
bool assignItem(df::item *item);
bool isValid();
df::building_type getType() { return building->getType(); }
bool isCurrentlySelectedBuilding() { return isValid() && (building == world->selected_building); }
ItemFilter *getFilter() { return &filter; }
void remove() { DFHack::World::DeletePersistentData(config); }
private:
df::building *building;
PersistentDataItem config;
df::coord pos;
ItemFilter filter;
};
class Planner
{
public:
bool in_dummmy_screen;
Planner() : in_dummmy_screen(false), quickfort_mode(false) { }
bool isPlanableBuilding(const df::building_type type) const
{
return item_for_building_type.find(type) != item_for_building_type.end();
}
void reset(color_ostream &out);
void initialize();
void addPlannedBuilding(df::building *bld)
{
for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++)
{
(*iter)->flags.bits.suspend = true;
}
PlannedBuilding pb(bld, &default_item_filters[bld->getType()]);
planned_buildings.push_back(pb);
}
void doCycle();
bool allocatePlannedBuilding(df::building_type type);
PlannedBuilding *getSelectedPlannedBuilding();
void removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); }
ItemFilter *getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; }
void adjustMinQuality(df::building_type type, int amount);
void adjustMaxQuality(df::building_type type, int amount);
void enableQuickfortMode()
{
saved_planmodes = planmode_enabled;
for_each_(planmode_enabled, enable_quickfort_fn);
quickfort_mode = true;
}
void disableQuickfortMode()
{
planmode_enabled = saved_planmodes;
quickfort_mode = false;
}
bool inQuickFortMode() { return quickfort_mode; }
private:
map<df::building_type, df::item_type> item_for_building_type;
map<df::building_type, ItemFilter> default_item_filters;
map<df::item_type, std::vector<df::item *>> available_item_vectors;
map<df::item_type, bool> is_relevant_item_type; //Needed for fast check when looping over all items
bool quickfort_mode;
std::vector<PlannedBuilding> planned_buildings;
void boundsCheckItemQuality(item_quality::item_quality *quality);
void gather_available_items()
{
debug("Gather available items");
for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++)
{
iter->second.clear();
}
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact);
#undef F
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
df::item_type itype = item->getType();
if (!is_relevant_item_type[itype])
continue;
if (itype == item_type::BOX && item->isBag())
continue; //Skip bags
if (item->flags.bits.artifact)
continue;
if (item->flags.bits.in_job ||
item->isAssignedToStockpile() ||
item->flags.bits.owned ||
item->flags.bits.in_chest)
{
continue;
}
available_item_vectors[itype].push_back(item);
}
}
};
extern Planner planner;
extern RoomMonitor roomMonitor;
#endif

@ -0,0 +1,552 @@
#include "df/general_ref_building_holderst.h"
#include "df/job_item.h"
#include "df/building_doorst.h"
#include "df/building_design.h"
#include "modules/Job.h"
#include "modules/Buildings.h"
#include "modules/Gui.h"
#include "buildingplan-planner.h"
#include "buildingplan-lib.h"
#include "uicommon.h"
/*
* ItemFilter
*/
ItemFilter::ItemFilter() :
min_quality(df::item_quality::Ordinary),
max_quality(df::item_quality::Artifact),
decorated_only(false),
valid(true)
{
clear(); // mat_mask is not cleared by default (see issue #1047)
}
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat)
{
return (mat_mask.whole) ? mat.matches(mat_mask) : true;
}
bool ItemFilter::matches(const df::dfhack_material_category mask) const
{
return mask.whole & mat_mask.whole;
}
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
{
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
bool ItemFilter::matches(df::item *item)
{
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);
}
std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); }
std::vector<std::string> ItemFilter::getMaterialFilterAsVector()
{
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::getMaterialFilterAsSerial()
{
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;
}
bool ItemFilter::parseSerializedMaterialTokens(std::string str)
{
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);
}
}
valid = true;
return true;
}
std::string ItemFilter::getMinQuality()
{
return ENUM_KEY_STR(item_quality, min_quality);
}
std::string ItemFilter::getMaxQuality()
{
return ENUM_KEY_STR(item_quality, max_quality);
}
bool ItemFilter::isValid()
{
return valid;
}
void ItemFilter::clear()
{
mat_mask.whole = 0;
materials.clear();
}
/*
* PlannedBuilding
*/
PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter)
{
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;
}
PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out)
{
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;
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;
}
bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
{
decltype(items_vector->begin()) closest_item;
int32_t closest_distance = -1;
for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++)
{
auto item = *item_iter;
if (!filter.matches(item))
continue;
auto pos = item->pos;
auto distance = abs(pos.x - building->centerx) +
abs(pos.y - building->centery) +
abs(pos.z - building->z) * 50;
if (closest_distance > -1 && distance >= closest_distance)
continue;
closest_distance = distance;
closest_item = item_iter;
}
if (closest_distance > -1 && assignItem(*closest_item))
{
debug("Item assigned");
items_vector->erase(closest_item);
remove();
return true;
}
return false;
}
void delete_item_fn(df::job_item *x) { delete x; }
bool PlannedBuilding::assignItem(df::item *item)
{
auto ref = df::allocate<df::general_ref_building_holderst>();
if (!ref)
{
Core::printerr("Could not allocate general_ref_building_holderst\n");
return false;
}
ref->building_id = building->id;
if (building->jobs.size() != 1)
return false;
auto job = building->jobs[0];
for_each_(job->job_items, delete_item_fn);
job->job_items.clear();
job->flags.bits.suspend = false;
bool rough = false;
Job::attachJobItem(job, item, df::job_item_ref::Hauled);
if (item->getType() == item_type::BOULDER)
rough = true;
building->mat_type = item->getMaterial();
building->mat_index = item->getMaterialIndex();
job->mat_type = building->mat_type;
job->mat_index = building->mat_index;
if (building->needsDesign())
{
auto act = (df::building_actual *) building;
act->design = new df::building_design();
act->design->flags.bits.rough = rough;
}
return true;
}
bool PlannedBuilding::isValid()
{
bool valid = filter.isValid() &&
building && Buildings::findAtTile(pos) == building &&
building->getBuildStage() == 0;
if (!valid)
remove();
return valid;
}
df::building_type PlannedBuilding::getType()
{
return building->getType();
}
bool PlannedBuilding::isCurrentlySelectedBuilding()
{
return isValid() && (building == df::global::world->selected_building);
}
ItemFilter *PlannedBuilding::getFilter()
{
return &filter;
}
void PlannedBuilding::remove()
{
DFHack::World::DeletePersistentData(config);
}
/*
* Planner
*/
Planner::Planner() : in_dummmy_screen(false), quickfort_mode(false) { }
void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; }
bool Planner::isPlanableBuilding(const df::building_type type) const
{
return item_for_building_type.find(type) != item_for_building_type.end();
}
void Planner::reset(color_ostream &out)
{
planned_buildings.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/constraints");
for (auto i = items.begin(); i != items.end(); i++)
{
PlannedBuilding pb(*i, out);
if (pb.isValid())
planned_buildings.push_back(pb);
}
}
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; \
if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \
planmode_enabled[df::building_type::btype] = false
FOR_ENUM_ITEMS(item_type, it)
is_relevant_item_type[it] = false;
add_building_type(Armorstand, ARMORSTAND);
add_building_type(Bed, BED);
add_building_type(Chair, CHAIR);
add_building_type(Coffin, COFFIN);
add_building_type(Door, DOOR);
add_building_type(Floodgate, FLOODGATE);
add_building_type(Hatch, HATCH_COVER);
add_building_type(GrateWall, GRATE);
add_building_type(GrateFloor, GRATE);
add_building_type(BarsVertical, BAR);
add_building_type(BarsFloor, BAR);
add_building_type(Cabinet, CABINET);
add_building_type(Box, BOX);
// skip kennels, farm plot
add_building_type(Weaponrack, WEAPONRACK);
add_building_type(Statue, STATUE);
add_building_type(Slab, SLAB);
add_building_type(Table, TABLE);
// skip roads ... furnaces
add_building_type(WindowGlass, WINDOW);
// skip gem window ... support
add_building_type(AnimalTrap, ANIMALTRAP);
add_building_type(Chain, CHAIN);
add_building_type(Cage, CAGE);
// skip archery target
add_building_type(TractionBench, TRACTION_BENCH);
// skip nest box, hive (tools)
#undef add_building_type
}
void Planner::addPlannedBuilding(df::building *bld)
{
for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++)
{
(*iter)->flags.bits.suspend = true;
}
PlannedBuilding pb(bld, &default_item_filters[bld->getType()]);
planned_buildings.push_back(pb);
}
void Planner::doCycle()
{
debug("Running Cycle");
if (planned_buildings.size() == 0)
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())
{
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()];
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);
}
}
bool Planner::allocatePlannedBuilding(df::building_type type)
{
coord32_t cursor;
if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z))
return false;
auto newinst = Buildings::allocInstance(cursor.get_coord16(), type);
if (!newinst)
return false;
df::job_item *filter = new df::job_item();
filter->item_type = item_type::NONE;
filter->mat_index = 0;
filter->flags2.bits.building_material = true;
std::vector<df::job_item*> filters;
filters.push_back(filter);
if (!Buildings::constructWithFilters(newinst, filters))
{
delete newinst;
return false;
}
if (type == building_type::Door)
{
auto door = virtual_cast<df::building_doorst>(newinst);
if (door)
door->door_flags.bits.pet_passable = true;
}
addPlannedBuilding(newinst);
return true;
}
PlannedBuilding *Planner::getSelectedPlannedBuilding()
{
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++)
{
if (building_iter->isCurrentlySelectedBuilding())
{
return &(*building_iter);
}
}
return nullptr;
}
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)
{
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;
}
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;
}
void Planner::enableQuickfortMode()
{
saved_planmodes = planmode_enabled;
for_each_(planmode_enabled, enable_quickfort_fn);
quickfort_mode = true;
}
void Planner::disableQuickfortMode()
{
planmode_enabled = saved_planmodes;
quickfort_mode = false;
}
bool Planner::inQuickFortMode() { return quickfort_mode; }
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;
}
void Planner::gather_available_items()
{
debug("Gather available items");
for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++)
{
iter->second.clear();
}
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact);
#undef F
std::vector<df::item*> &items = df::global::world->items.other[df::items_other_id::IN_PLAY];
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
df::item_type itype = item->getType();
if (!is_relevant_item_type[itype])
continue;
if (itype == df::item_type::BOX && item->isBag())
continue; //Skip bags
if (item->flags.bits.artifact)
continue;
if (item->flags.bits.in_job ||
item->isAssignedToStockpile() ||
item->flags.bits.owned ||
item->flags.bits.in_chest)
{
continue;
}
available_item_vectors[itype].push_back(item);
}
}
std::map<df::building_type, bool> planmode_enabled, saved_planmodes;
Planner planner;

@ -0,0 +1,108 @@
#pragma once
#include "df/item_quality.h"
#include "df/dfhack_material_category.h"
#include "modules/Materials.h"
#include "modules/Persistence.h"
struct ItemFilter
{
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;
};
class PlannedBuilding
{
public:
PlannedBuilding(df::building *building, ItemFilter *filter);
PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out);
bool assignClosestItem(std::vector<df::item *> *items_vector);
bool assignItem(df::item *item);
bool isValid();
void remove();
df::building_type getType();
bool isCurrentlySelectedBuilding();
ItemFilter *getFilter();
private:
df::building *building;
DFHack::PersistentDataItem config;
df::coord pos;
ItemFilter filter;
};
class Planner
{
public:
bool in_dummmy_screen;
Planner();
bool isPlanableBuilding(const df::building_type type) const;
void reset(DFHack::color_ostream &out);
void initialize();
void addPlannedBuilding(df::building *bld);
void doCycle();
bool allocatePlannedBuilding(df::building_type type);
PlannedBuilding *getSelectedPlannedBuilding();
void removeSelectedPlannedBuilding();
ItemFilter *getDefaultItemFilterForType(df::building_type type);
void adjustMinQuality(df::building_type type, int amount);
void adjustMaxQuality(df::building_type type, int amount);
void enableQuickfortMode();
void disableQuickfortMode();
bool inQuickFortMode();
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::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
bool quickfort_mode;
std::vector<PlannedBuilding> planned_buildings;
void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality);
void gather_available_items();
};
extern std::map<df::building_type, bool> planmode_enabled, saved_planmodes;
extern Planner planner;

@ -0,0 +1,227 @@
#include "buildingplan-rooms.h"
#include "buildingplan-lib.h"
#include <df/entity_position.h>
#include <df/job_type.h>
#include <df/world.h>
#include <modules/World.h>
#include <modules/Units.h>
#include <modules/Buildings.h>
using namespace DFHack;
bool canReserveRoom(df::building *building)
{
if (!building)
return false;
if (building->jobs.size() > 0 && building->jobs[0]->job_type == df::job_type::DestroyBuilding)
return false;
return building->is_room;
}
std::vector<Units::NoblePosition> getUniqueNoblePositions(df::unit *unit)
{
std::vector<Units::NoblePosition> np;
Units::getNoblePositions(&np, unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == "MILITIA_CAPTAIN")
{
np.erase(iter);
break;
}
}
return np;
}
/*
* ReservedRoom
*/
ReservedRoom::ReservedRoom(df::building *building, std::string noble_code)
{
this->building = building;
config = DFHack::World::AddPersistentData("buildingplan/reservedroom");
config.val() = noble_code;
config.ival(1) = building->id;
pos = df::coord(building->centerx, building->centery, building->z);
}
ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &)
{
this->config = config;
building = df::building::find(config.ival(1));
if (!building)
return;
pos = df::coord(building->centerx, building->centery, building->z);
}
bool ReservedRoom::checkRoomAssignment()
{
if (!isValid())
return false;
auto np = getOwnersNobleCode();
bool correctOwner = false;
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
correctOwner = true;
break;
}
}
if (correctOwner)
return true;
for (auto iter = df::global::world->units.active.begin(); iter != df::global::world->units.active.end(); iter++)
{
df::unit* unit = *iter;
if (!Units::isCitizen(unit))
continue;
if (!Units::isActive(unit))
continue;
np = getUniqueNoblePositions(unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
Buildings::setOwner(building, unit);
break;
}
}
}
return true;
}
void ReservedRoom::remove() { DFHack::World::DeletePersistentData(config); }
bool ReservedRoom::isValid()
{
if (!building)
return false;
if (Buildings::findAtTile(pos) != building)
return false;
return canReserveRoom(building);
}
int32_t ReservedRoom::getId()
{
if (!isValid())
return 0;
return building->id;
}
std::string ReservedRoom::getCode() { return config.val(); }
void ReservedRoom::setCode(const std::string &noble_code) { config.val() = noble_code; }
std::vector<Units::NoblePosition> ReservedRoom::getOwnersNobleCode()
{
if (!building->owner)
return std::vector<Units::NoblePosition> ();
return getUniqueNoblePositions(building->owner);
}
/*
* RoomMonitor
*/
std::string RoomMonitor::getReservedNobleCode(int32_t buildingId)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId == iter->getId())
return iter->getCode();
}
return "";
}
void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code)
{
bool found = false;
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId != iter->getId())
{
continue;
}
else
{
if (noble_code == iter->getCode())
{
iter->remove();
reservedRooms.erase(iter);
}
else
{
iter->setCode(noble_code);
}
found = true;
break;
}
}
if (!found)
{
ReservedRoom room(df::building::find(buildingId), noble_code);
reservedRooms.push_back(room);
}
}
void RoomMonitor::doCycle()
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();)
{
if (iter->checkRoomAssignment())
{
++iter;
}
else
{
iter->remove();
iter = reservedRooms.erase(iter);
}
}
}
void RoomMonitor::reset(color_ostream &out)
{
reservedRooms.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom");
for (auto i = items.begin(); i != items.end(); i++)
{
ReservedRoom rr(*i, out);
if (rr.isValid())
addRoom(rr);
}
}
void RoomMonitor::addRoom(ReservedRoom &rr)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (iter->getId() == rr.getId())
return;
}
reservedRooms.push_back(rr);
}
RoomMonitor roomMonitor;

@ -0,0 +1,51 @@
#pragma once
#include "modules/Persistence.h"
#include "modules/Units.h"
class ReservedRoom
{
public:
ReservedRoom(df::building *building, std::string noble_code);
ReservedRoom(DFHack::PersistentDataItem &config, DFHack::color_ostream &out);
bool checkRoomAssignment();
void remove();
bool isValid();
int32_t getId();
std::string getCode();
void setCode(const std::string &noble_code);
private:
df::building *building;
DFHack::PersistentDataItem config;
df::coord pos;
std::vector<DFHack::Units::NoblePosition> getOwnersNobleCode();
};
class RoomMonitor
{
public:
RoomMonitor() { }
std::string getReservedNobleCode(int32_t buildingId);
void toggleRoomForPosition(int32_t buildingId, std::string noble_code);
void doCycle();
void reset(DFHack::color_ostream &out);
private:
std::vector<ReservedRoom> reservedRooms;
void addRoom(ReservedRoom &rr);
};
bool canReserveRoom(df::building *building);
std::vector<DFHack::Units::NoblePosition> getUniqueNoblePositions(df::unit *unit);
extern RoomMonitor roomMonitor;

@ -1,41 +1,297 @@
#include "LuaTools.h"
#include "buildingplan-lib.h" #include "buildingplan-lib.h"
#include "df/entity_position.h"
#include "df/interface_key.h"
#include "df/ui_build_selector.h"
#include "df/viewscreen_dwarfmodest.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/World.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "uicommon.h"
#include "listcolumn.h"
DFHACK_PLUGIN("buildingplan"); DFHACK_PLUGIN("buildingplan");
#define PLUGIN_VERSION 0.14 #define PLUGIN_VERSION 0.15
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(ui_build_selector);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) #define MAX_MASK 10
{ #define MAX_MATERIAL 21
return CR_OK;
}
using namespace DFHack;
using namespace df::enums;
static bool is_planmode_enabled(df::building_type type) bool show_help = false;
class ViewscreenChooseMaterial : public dfhack_viewscreen
{ {
if (planmode_enabled.find(type) == planmode_enabled.end()) public:
ViewscreenChooseMaterial(ItemFilter *filter);
void feed(set<df::interface_key> *input);
void render();
std::string getFocusString() { return "buildingplan_choosemat"; }
private:
ListColumn<df::dfhack_material_category> masks_column;
ListColumn<MaterialInfo> materials_column;
int selected_column;
ItemFilter *filter;
df::building_type btype;
void addMaskEntry(df::dfhack_material_category &mask, const std::string &text)
{ {
return false; auto entry = ListEntry<df::dfhack_material_category>(pad_string(text, MAX_MASK, false), mask);
if (filter->matches(mask))
entry.selected = true;
masks_column.add(entry);
} }
return planmode_enabled[type]; void populateMasks()
{
masks_column.clear();
df::dfhack_material_category mask;
mask.whole = 0;
mask.bits.stone = true;
addMaskEntry(mask, "Stone");
mask.whole = 0;
mask.bits.wood = true;
addMaskEntry(mask, "Wood");
mask.whole = 0;
mask.bits.metal = true;
addMaskEntry(mask, "Metal");
mask.whole = 0;
mask.bits.soap = true;
addMaskEntry(mask, "Soap");
masks_column.filterDisplay();
}
void populateMaterials()
{
materials_column.clear();
df::dfhack_material_category selected_category;
std::vector<df::dfhack_material_category> selected_masks = masks_column.getSelectedElems();
if (selected_masks.size() == 1)
selected_category = selected_masks[0];
else if (selected_masks.size() > 1)
return;
df::world_raws &raws = world->raws;
for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++)
{
auto obj = raws.mat_table.builtin[i];
if (obj)
{
MaterialInfo material;
material.decode(i, -1);
addMaterialEntry(selected_category, material, material.toString());
}
}
for (size_t i = 0; i < raws.inorganics.size(); i++)
{
df::inorganic_raw *p = raws.inorganics[i];
MaterialInfo material;
material.decode(0, i);
addMaterialEntry(selected_category, material, material.toString());
}
decltype(selected_category) wood_flag;
wood_flag.bits.wood = true;
if (!selected_category.whole || selected_category.bits.wood)
{
for (size_t i = 0; i < raws.plants.all.size(); i++)
{
df::plant_raw *p = raws.plants.all[i];
for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++)
{
auto t = p->material[j];
if (p->material[j]->id != "WOOD")
continue;
MaterialInfo material;
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))
entry.selected = true;
materials_column.add(entry);
}
}
}
materials_column.sort();
}
void addMaterialEntry(df::dfhack_material_category &selected_category,
MaterialInfo &material, std::string name)
{
if (!selected_category.whole || material.matches(selected_category))
{
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
entry.selected = true;
materials_column.add(entry);
}
}
void validateColumn()
{
set_to_limit(selected_column, 1);
}
void resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
masks_column.resize();
materials_column.resize();
}
};
DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; }
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter)
{
selected_column = 0;
masks_column.setTitle("Type");
masks_column.multiselect = true;
masks_column.allow_search = false;
masks_column.left_margin = 2;
materials_column.left_margin = MAX_MASK + 3;
materials_column.setTitle("Material");
materials_column.multiselect = true;
this->filter = filter;
masks_column.changeHighlight(0);
populateMasks();
populateMaterials();
masks_column.selectDefaultEntry();
materials_column.selectDefaultEntry();
materials_column.changeHighlight(0);
} }
#define DAY_TICKS 1200 void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
DFhackCExport command_result plugin_onupdate(color_ostream &out)
{ {
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0) bool key_processed = false;
switch (selected_column)
{ {
planner.doCycle(); case 0:
roomMonitor.doCycle(); key_processed = masks_column.feed(input);
if (input->count(interface_key::SELECT))
populateMaterials(); // Redo materials lists based on category selection
break;
case 1:
key_processed = materials_column.feed(input);
break;
} }
return CR_OK; if (key_processed)
return;
if (input->count(interface_key::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
if (input->count(interface_key::CUSTOM_SHIFT_C))
{
filter->clear();
masks_column.clearSelection();
materials_column.clearSelection();
populateMaterials();
}
else if (input->count(interface_key::SEC_SELECT))
{
// Convert list selections to material filters
filter->mat_mask.whole = 0;
filter->materials.clear();
// Category masks
auto masks = masks_column.getSelectedElems();
for (auto it = masks.begin(); it != masks.end(); ++it)
filter->mat_mask.whole |= it->whole;
// Specific materials
auto materials = materials_column.getSelectedElems();
transform_(materials, filter->materials, material_info_identity_fn);
Screen::dismiss(this);
}
else if (input->count(interface_key::CURSOR_LEFT))
{
--selected_column;
validateColumn();
}
else if (input->count(interface_key::CURSOR_RIGHT))
{
selected_column++;
validateColumn();
}
else if (enabler->tracking_on && enabler->mouse_lbut)
{
if (masks_column.setHighlightByMouse())
selected_column = 0;
else if (materials_column.setHighlightByMouse())
selected_column = 1;
enabler->mouse_lbut = enabler->mouse_rbut = 0;
}
}
void ViewscreenChooseMaterial::render()
{
if (Screen::isDismissed(this))
return;
dfhack_viewscreen::render();
Screen::clear();
Screen::drawBorder(" Building Material ");
masks_column.display(selected_column == 0);
materials_column.display(selected_column == 1);
int32_t y = gps->dimy - 3;
int32_t x = 2;
OutputHotkeyString(x, y, "Toggle", interface_key::SELECT);
x += 3;
OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT);
x += 3;
OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C);
x += 3;
OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN);
} }
//START Viewscreen Hook //START Viewscreen Hook
static bool is_planmode_enabled(df::building_type type)
{
if (planmode_enabled.find(type) == planmode_enabled.end())
{
return false;
}
return planmode_enabled[type];
}
struct buildingplan_hook : public df::viewscreen_dwarfmodest struct buildingplan_hook : public df::viewscreen_dwarfmodest
{ {
//START UI Methods //START UI Methods
@ -58,8 +314,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
bool isInPlannedBuildingPlacementMode() bool isInPlannedBuildingPlacementMode()
{ {
return ui->main.mode == ui_sidebar_mode::Build && return ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector && df::global::ui_build_selector &&
ui_build_selector->stage < 2 && df::global::ui_build_selector->stage < 2 &&
planner.isPlanableBuilding(ui_build_selector->building_type); planner.isPlanableBuilding(ui_build_selector->building_type);
} }
@ -350,7 +606,6 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render);
static command_result buildingplan_cmd(color_ostream &out, vector <string> & parameters) static command_result buildingplan_cmd(color_ostream &out, vector <string> & parameters)
{ {
if (!parameters.empty()) if (!parameters.empty())
@ -401,7 +656,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {
@ -416,6 +670,23 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK; return CR_OK;
} }
#define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &)
{
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0)
{
planner.doCycle();
roomMonitor.doCycle();
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &)
{
return CR_OK;
}
// Lua API section // Lua API section
static bool isPlannableBuilding(df::building_type type) { static bool isPlannableBuilding(df::building_type type) {

@ -1,11 +1,25 @@
#include "buildingplan-lib.h"
#include <fstream> #include <fstream>
#include <vector> #include <vector>
#include "df/world.h"
#include "df/trap_type.h"
#include "modules/Filesystem.h" #include "modules/Filesystem.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/World.h"
#include "PluginManager.h"
#include "buildingplan-lib.h"
#include "uicommon.h"
DFHACK_PLUGIN("fortplan"); DFHACK_PLUGIN("fortplan");
#define PLUGIN_VERSION 0.15 #define PLUGIN_VERSION 0.15
using namespace std;
using namespace DFHack;
command_result fortplan(color_ostream &out, vector<string> & params); command_result fortplan(color_ostream &out, vector<string> & params);
struct BuildingInfo { struct BuildingInfo {
@ -88,7 +102,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
#define DAY_TICKS 1200 #define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &out) DFhackCExport command_result plugin_onupdate(color_ostream &out)
{ {
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0) if (Maps::IsValid() && !World::ReadPauseState() && df::global::world->frame_counter % (DAY_TICKS/2) == 0)
{ {
planner.doCycle(); planner.doCycle();
} }
@ -100,7 +114,7 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{ {
if (!gps) if (!df::global::gps )
return CR_FAILURE; return CR_FAILURE;
if (enable != is_enabled) if (enable != is_enabled)