Merge remote-tracking branch 'upstream/develop' into buildingplan_isenabled

develop
Myk Taylor 2020-09-23 19:21:16 -07:00
commit 72fb6d09d6
14 changed files with 1351 additions and 1235 deletions

@ -1 +1 @@
Subproject commit 26839cce4534ea4ee70ba992a6d7a774c624d584
Subproject commit 3c0f2e86ce4e7a3a3b30e765087d02a68bba7e6f

@ -1 +1 @@
Subproject commit 33c059d2217de6a5271e9ecbf19908e6efbf0e79
Subproject commit be76fa5086bfe6b1a5e83c9855e39f98edc1f066

@ -1831,9 +1831,27 @@ Screen API
The screen module implements support for drawing to the tiled screen of the game.
Note that drawing only has any effect when done from callbacks, so it can only
be feasibly used in the core context.
be feasibly used in the `core context <lua-core-context>`.
Basic painting functions:
.. contents::
:local:
Basic painting functions
~~~~~~~~~~~~~~~~~~~~~~~~
Common parameters to these functions include:
* ``x``, ``y``: screen coordinates in tiles; the upper left corner of the screen
is ``x = 0, y = 0``
* ``pen``: a `pen object <lua-screen-pen>`
* ``map``: a boolean indicating whether to draw to a separate map buffer
(defaults to false, which is suitable for off-map text or a screen that hides
the map entirely). Note that only third-party plugins like TWBT currently
implement a separate map buffer. If no such plugins are enabled, passing
``true`` has no effect. However, this parameter should still be used to ensure
that scripts work properly with such plugins.
Functions:
* ``dfhack.screen.getWindowSize()``
@ -1849,25 +1867,25 @@ Basic painting functions:
* ``dfhack.screen.paintTile(pen,x,y[,char,tile,map])``
Paints a tile using given parameters. See below for a description of pen.
Paints a tile using given parameters. `See below <lua-screen-pen>` for a description of ``pen``.
Returns *false* if coordinates out of bounds, or other error.
Returns *false* on error, e.g. if coordinates are out of bounds
* ``dfhack.screen.readTile(x,y[,map])``
Retrieves the contents of the specified tile from the screen buffers.
Returns a pen object, or *nil* if invalid or TrueType.
Returns a `pen object <lua-screen-pen>`, or *nil* if invalid or TrueType.
* ``dfhack.screen.paintString(pen,x,y,text[,map])``
Paints the string starting at *x,y*. Uses the string characters
in sequence to override the ``ch`` field of pen.
in sequence to override the ``ch`` field of `pen <lua-screen-pen>`.
Returns *true* if painting at least one character succeeded.
* ``dfhack.screen.fillRect(pen,x1,y1,x2,y2[,map])``
Fills the rectangle specified by the coordinates with the given pen.
Fills the rectangle specified by the coordinates with the given `pen <lua-screen-pen>`.
Returns *true* if painting at least one character succeeded.
* ``dfhack.screen.findGraphicsTile(pagename,x,y)``
@ -1903,7 +1921,12 @@ Basic painting functions:
Returns the keybinding representing the given string input
character, or *nil* if impossible.
The "pen" argument used by functions above may be represented by
.. _lua-screen-pen:
Pen API
~~~~~~~
The ``pen`` argument used by ``dfhack.screen`` functions may be represented by
a table with the following possible fields:
``ch``
@ -1958,6 +1981,9 @@ Alternatively, it may be a pre-parsed native object with the following API:
assigning to ``pen.tile_color`` also resets ``pen.tile_fg`` and
``pen.tile_bg`` to *nil*.
Screen management
~~~~~~~~~~~~~~~~~
In order to actually be able to paint to the screen, it is necessary
to create and register a viewscreen (basically a modal dialog) with
the game.
@ -1986,7 +2012,11 @@ Apart from a native viewscreen object, these functions accept a table
as a screen. In this case, ``show`` creates a new native viewscreen
that delegates all processing to methods stored in that table.
.. note:: Lua-implemented screens are only supported in the core context.
.. note::
* The `gui.Screen class <lua-gui-screen>` provides stubs for all of the
functions listed below, and its use is recommended
* Lua-implemented screens are only supported in the `core context <lua-core-context>`.
Supported callbacks and fields are:
@ -2314,11 +2344,13 @@ and are only documented here for completeness:
Returns a numeric identifier of the current thread.
.. _lua-core-context:
Core interpreter context
========================
While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
there is one special context managed by the DFHack core. It is the
only context that can receive events from DF and plugins.
Core context specific functions:
@ -2348,7 +2380,8 @@ Core context specific functions:
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
Creates a handler for state change events. Receives the same
`SC_ codes <lua-globals>` as ``plugin_onstatechange()`` in C++.
Event type
@ -2360,7 +2393,7 @@ through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.
This type itself is available in any context, but only the
core context has the actual events defined by C++ code.
`core context <lua-core-context>` has the actual events defined by C++ code.
Features:
@ -2427,6 +2460,8 @@ The following module management functions are provided:
should be kept limited to the standard Lua library and API described
in this document.
.. _lua-globals:
Global environment
==================
@ -2443,9 +2478,9 @@ environment by the mandatory init file dfhack.lua:
COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN, COLOR_LIGHTRED,
COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE
* ``dfhack.onStateChange`` event codes
* State change event codes, used by ``dfhack.onStateChange``
Available only in the core context, as is the event itself:
Available only in the `core context <lua-core-context>`, as is the event itself:
SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED,
SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED
@ -2964,7 +2999,9 @@ The painting natives in ``dfhack.screen`` apply to the whole screen, are
completely stateless and don't implement clipping.
The Painter class inherits from ViewRect to provide clipping and local
coordinates, and tracks current cursor position and current pen.
coordinates, and tracks current cursor position and current pen. It also
supports drawing to a separate map buffer if applicable (see ``map()`` below
for details).
* ``Painter{ ..., pen = ..., key_pen = ... }``
@ -2989,7 +3026,15 @@ coordinates, and tracks current cursor position and current pen.
* ``painter:cursor()``
Returns the current cursor *x,y* in local coordinates.
Returns the current cursor *x,y* in screen coordinates.
* ``painter:cursorX()``
Returns just the current *x* cursor coordinate
* ``painter:cursorY()``
Returns just the current *y* cursor coordinate
* ``painter:seek(x,y)``
@ -3009,10 +3054,22 @@ coordinates, and tracks current cursor position and current pen.
Sets the current pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*.
* ``painter:color(fg[,bold[,bg]])``
Sets the specified colors of the current pen and returns *self*.
* ``painter:key_pen(...)``
Sets the current keybinding pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*.
* ``painter:map(to_map)``
Enables or disables drawing to a separate map buffer. ``to_map`` is a boolean
that will be passed as the ``map`` parameter to any ``dfhack.screen`` functions
that accept it. Note that only third-party plugins like TWBT currently implement
a separate map buffer; if none are enabled, this function has no effect (but
should still be used to ensure proper support for such plugins). Returns *self*.
* ``painter:clear()``
Fills the whole clip rectangle with ``CLEAR_PEN``, and returns *self*.
@ -3029,7 +3086,7 @@ coordinates, and tracks current cursor position and current pen.
* ``painter:tile([char, tile[, ...]])``
Like above, but also allows overriding the ``tile`` property on ad-hoc basis.
Like ``char()`` above, but also allows overriding the ``tile`` property on ad-hoc basis.
* ``painter:string(text[, ...])``
@ -3039,7 +3096,13 @@ coordinates, and tracks current cursor position and current pen.
Paints the description of the keycode using ``dfhack.pen.parse(cur_key_pen,...)``; returns *self*.
As noted above, all painting methods return *self*, in order to allow chaining them like this::
* ``painter:key_string(keycode, text, ...)``
A convenience wrapper around both ``key()`` and ``string()`` that prints both
the specified keycode description and text, separated by ``:``. Any extra
arguments are passed directly to ``string()``. Returns *self*.
Unless specified otherwise above, all Painter methods return *self*, in order to allow chaining them like this::
painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')...
@ -3160,6 +3223,8 @@ The class has the following methods:
Returns *true* if any of the subviews handled the event.
.. _lua-gui-screen:
Screen class
------------
@ -4200,7 +4265,7 @@ Arguments are passed in to the scripts via the **...** built-in
quasi-variable; when the script is called by the DFHack core,
they are all guaranteed to be non-nil strings.
DFHack core invokes the scripts in the *core context* (see above);
DFHack core invokes the scripts in the `core context <lua-core-context>`;
however it is possible to call them from any lua code (including
from other scripts) in any context, via the same function the core uses:

@ -355,15 +355,15 @@ function Painter:string(text,pen,...)
return self:advance(#text, nil)
end
function Painter:key(code,pen,...)
function Painter:key(keycode,pen,...)
return self:string(
getKeyDisplay(code),
getKeyDisplay(keycode),
to_pen(self.cur_key_pen, pen, ...)
)
end
function Painter:key_string(code, text, ...)
return self:key(code):string(': '):string(text, ...)
function Painter:key_string(keycode, text, ...)
return self:key(keycode):string(': '):string(text, ...)
end
--------------------------

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

@ -1,690 +1,16 @@
#include "buildingplan-lib.h"
#define PLUGIN_VERSION 0.00
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");
#include "Core.h"
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, "/");
using namespace DFHack;
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();
}
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
}
bool show_debugging = false;
void Planner::doCycle()
void debug(const std::string &msg)
{
debug("Running Cycle");
if (planned_buildings.size() == 0)
if (!show_debugging)
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::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;
color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG: " << msg << endl;
}
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
#define BUILDINGPLAN_H
#pragma once
#include "uicommon.h"
#include "listcolumn.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);
#include "buildingplan-planner.h"
#include "buildingplan-rooms.h"
void debug(const std::string &msg);
std::string material_to_string_fn(MaterialInfo m);
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 "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");
#define PLUGIN_VERSION 0.14
#define PLUGIN_VERSION 0.15
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_build_selector);
REQUIRE_GLOBAL(world);
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
#define MAX_MASK 10
#define MAX_MATERIAL 21
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
DFhackCExport command_result plugin_onupdate(color_ostream &out)
void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
{
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0)
bool key_processed = false;
switch (selected_column)
{
planner.doCycle();
roomMonitor.doCycle();
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;
}
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
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
{
//START UI Methods
@ -58,8 +314,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
bool isInPlannedBuildingPlacementMode()
{
return ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector &&
ui_build_selector->stage < 2 &&
df::global::ui_build_selector &&
df::global::ui_build_selector->stage < 2 &&
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, render);
static command_result buildingplan_cmd(color_ostream &out, vector <string> & parameters)
{
if (!parameters.empty())
@ -401,7 +656,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
@ -416,6 +670,23 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
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
static bool isPlannableBuilding(df::building_type type) {

@ -1,11 +1,28 @@
#include "buildingplan-lib.h"
#include <fstream>
#include <vector>
#include "df/world.h"
#include "df/trap_type.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");
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world);
#define PLUGIN_VERSION 0.15
using namespace std;
using namespace DFHack;
command_result fortplan(color_ostream &out, vector<string> & params);
struct BuildingInfo {
@ -88,7 +105,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
#define DAY_TICKS 1200
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();
}
@ -100,7 +117,7 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (!gps)
if (!df::global::gps )
return CR_FAILURE;
if (enable != is_enabled)

@ -1 +1 @@
Subproject commit d069acb3ab7660c18cbb789070186f0757775462
Subproject commit 436b98f6b2dd8a8a845e5eccaeaecd5024dc1b9f