dfhack/plugins/buildingplan.cpp

1291 lines
35 KiB
C++

// Auto Material Select
#include <map>
#include <string>
#include <vector>
#include <algorithm>
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "MiscUtils.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/enabler.h"
#include "df/building_design.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/Items.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"
using std::map;
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::ui;
using df::global::ui_build_selector;
using df::global::world;
using df::global::enabler;
DFHACK_PLUGIN("buildingplan");
#define PLUGIN_VERSION 0.2
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;
}
};
static command_result automaterial_cmd(color_ostream &out, vector <string> & parameters)
{
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0)
{
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
if (newline)
{
++y;
x = left_margin;
}
else
x += text.length();
}
void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t color = COLOR_WHITE)
{
OutputString(10, x, y, hotkey);
string display(": ");
display.append(text);
OutputString(color, x, y, display, newline, left_margin);
}
void OutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE)
{
OutputHotkeyString(x, y, text, hotkey);
OutputString(COLOR_WHITE, x, y, ": ");
if (state)
OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin);
else
OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin);
}
struct coord32_t
{
int32_t x, y, z;
df::coord get_coord16() const
{
return df::coord(x, y, z);
}
};
typedef int8_t UIColor;
const int ascii_to_enum_offset = interface_key::STRING_A048 - '0';
inline string int_to_string(const int n)
{
return static_cast<ostringstream*>( &(ostringstream() << n) )->str();
}
static void set_to_limit(int &value, const int maximum, const int min = 0)
{
if (value < min)
value = min;
else if (value > maximum)
value = maximum;
}
inline void paint_text(const UIColor color, const int &x, const int &y, const std::string &text, const UIColor background = 0)
{
Screen::paintString(Screen::Pen(' ', color, background), x, y, text);
}
static string pad_string(string text, const int size, const bool front = true, const bool trim = false)
{
if (text.length() > size)
{
if (trim && size > 10)
{
text = text.substr(0, size-3);
text.append("...");
}
return text;
}
string aligned(size - text.length(), ' ');
if (front)
{
aligned.append(text);
return aligned;
}
else
{
text.append(aligned);
return text;
}
}
#define MAX_MASK 10
#define MAX_MATERIAL 21
#define SIDEBAR_WIDTH 30
#define COLOR_TITLE COLOR_BLUE
#define COLOR_UNSELECTED COLOR_GREY
#define COLOR_SELECTED COLOR_WHITE
#define COLOR_HIGHLIGHTED COLOR_GREEN
/*
* List classes
*/
template <typename T>
class ListEntry
{
public:
T elem;
string text;
bool selected;
ListEntry(string text, T elem)
{
this->text = text;
this->elem = elem;
selected = false;
}
};
template <typename T>
class ListColumn
{
public:
string title;
int highlighted_index;
int display_start_offset;
int32_t bottom_margin, search_margin, left_margin;
bool search_entry_mode;
bool multiselect;
bool allow_null;
bool auto_select;
bool force_sort;
ListColumn()
{
clear();
left_margin = 2;
bottom_margin = 3;
search_margin = 38;
highlighted_index = 0;
multiselect = false;
allow_null = true;
auto_select = false;
search_entry_mode = false;
force_sort = false;
}
void clear()
{
list.clear();
display_list.clear();
display_start_offset = 0;
max_item_width = 0;
resize();
}
void resize()
{
display_max_rows = gps->dimy - 4 - bottom_margin;
}
void add(ListEntry<T> &entry)
{
list.push_back(entry);
if (entry.text.length() > max_item_width)
max_item_width = entry.text.length();
}
void add(const string &text, T &elem)
{
list.push_back(ListEntry<T>(text, elem));
if (text.length() > max_item_width)
max_item_width = text.length();
}
virtual void display_extras(const T &elem, int32_t &x, int32_t &y) const {}
void display(const bool is_selected_column) const
{
int32_t y = 2;
paint_text(COLOR_TITLE, left_margin, y, title);
int last_index_able_to_display = display_start_offset + display_max_rows;
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
{
++y;
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : COLOR_UNSELECTED;
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
paint_text(fg_color, left_margin, y, display_list[i]->text, bg_color);
int x = left_margin + display_list[i]->text.length() + 1;
display_extras(display_list[i]->elem, x, y);
}
if (is_selected_column)
{
y = gps->dimy - bottom_margin;
int32_t x = search_margin;
OutputHotkeyString(x, y, "Search" ,"S");
if (!search_string.empty() || search_entry_mode)
{
OutputString(COLOR_WHITE, x, y, ": ");
OutputString(COLOR_WHITE, x, y, search_string);
if (search_entry_mode)
OutputString(COLOR_LIGHTGREEN, x, y, "_");
}
}
}
void filter_display()
{
ListEntry<T> *prev_selected = (getDisplayListSize() > 0) ? display_list[highlighted_index] : NULL;
display_list.clear();
for (size_t i = 0; i < list.size(); i++)
{
if (search_string.empty() || list[i].text.find(search_string) != string::npos)
{
ListEntry<T> *entry = &list[i];
display_list.push_back(entry);
if (entry == prev_selected)
highlighted_index = display_list.size() - 1;
}
}
changeHighlight(0);
}
void selectDefaultEntry()
{
for (size_t i = 0; i < display_list.size(); i++)
{
if (display_list[i]->selected)
{
highlighted_index = i;
break;
}
}
}
void validateHighlight()
{
set_to_limit(highlighted_index, display_list.size() - 1);
if (highlighted_index < display_start_offset)
display_start_offset = highlighted_index;
else if (highlighted_index >= display_start_offset + display_max_rows)
display_start_offset = highlighted_index - display_max_rows + 1;
if (auto_select || (!allow_null && list.size() == 1))
display_list[highlighted_index]->selected = true;
}
void changeHighlight(const int highlight_change, const int offset_shift = 0)
{
if (!initHighlightChange())
return;
highlighted_index += highlight_change + offset_shift * display_max_rows;
display_start_offset += offset_shift * display_max_rows;
set_to_limit(display_start_offset, max(0, (int)(display_list.size())-display_max_rows));
validateHighlight();
}
void setHighlight(const int index)
{
if (!initHighlightChange())
return;
highlighted_index = index;
validateHighlight();
}
bool initHighlightChange()
{
if (display_list.size() == 0)
return false;
if (auto_select && !multiselect)
{
for (typename vector< ListEntry<T> >::iterator it = list.begin(); it != list.end(); it++)
{
it->selected = false;
}
}
return true;
}
void toggleHighlighted()
{
if (auto_select)
return;
ListEntry<T> *entry = display_list[highlighted_index];
if (!multiselect || !allow_null)
{
int selected_count = 0;
for (size_t i = 0; i < list.size(); i++)
{
if (!multiselect && !entry->selected)
list[i].selected = false;
if (!allow_null && list[i].selected)
selected_count++;
}
if (!allow_null && entry->selected && selected_count == 1)
return;
}
entry->selected = !entry->selected;
}
vector<T*> getSelectedElems(bool only_one = false)
{
vector<T*> results;
for (typename vector< ListEntry<T> >::iterator it = list.begin(); it != list.end(); it++)
{
if ((*it).selected)
{
results.push_back(&(*it).elem);
if (only_one)
break;
}
}
return results;
}
T* getFirstSelectedElem()
{
vector<T*> results = getSelectedElems(true);
if (results.size() == 0)
return NULL;
else
return results[0];
}
size_t getDisplayListSize()
{
return display_list.size();
}
size_t getBaseListSize()
{
return list.size();
}
bool feed(set<df::interface_key> *input)
{
if (input->count(interface_key::CURSOR_UP))
{
search_entry_mode = false;
changeHighlight(-1);
}
else if (input->count(interface_key::CURSOR_DOWN))
{
search_entry_mode = false;
changeHighlight(1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEUP))
{
search_entry_mode = false;
changeHighlight(0, -1);
}
else if (input->count(interface_key::STANDARDSCROLL_PAGEDOWN))
{
search_entry_mode = false;
changeHighlight(0, 1);
}
else if (search_entry_mode)
{
// Search query typing mode
df::interface_key last_token = *input->rbegin();
if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126)
{
// Standard character
search_string += last_token - ascii_to_enum_offset;
filter_display();
}
else if (last_token == interface_key::STRING_A000)
{
// Backspace
if (search_string.length() > 0)
{
search_string.erase(search_string.length()-1);
filter_display();
}
}
else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN))
{
// ENTER or ESC: leave typing mode
search_entry_mode = false;
}
else if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT))
{
// Arrow key pressed. Leave entry mode and allow screen to process key
search_entry_mode = false;
return false;
}
return true;
}
// Not in search query typing mode
else if (input->count(interface_key::SELECT) && !auto_select)
{
toggleHighlighted();
}
else if (input->count(interface_key::CUSTOM_S))
{
search_entry_mode = true;
}
else if (input->count(interface_key::CUSTOM_SHIFT_S))
{
search_string.clear();
filter_display();
}
else if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1 && enabler->mouse_lbut)
{
return setHighlightByMouse();
}
else
return false;
return true;
}
bool setHighlightByMouse()
{
if (gps->mouse_y >= 3 && gps->mouse_y < display_max_rows + 3 &&
gps->mouse_x >= left_margin && gps->mouse_x < left_margin + max_item_width)
{
int new_index = display_start_offset + gps->mouse_y - 3;
if (new_index < display_list.size())
setHighlight(new_index);
enabler->mouse_lbut = enabler->mouse_rbut = 0;
return true;
}
return false;
}
static bool compareText(ListEntry<T> const& a, ListEntry<T> const& b)
{
return a.text.compare(b.text) < 0;
}
void doSort(bool (*function)(ListEntry<T> const&, ListEntry<T> const&))
{
if (force_sort || list.size() < 100)
std::sort(list.begin(), list.end(), function);
filter_display();
}
virtual void sort()
{
doSort(&compareText);
}
private:
vector< ListEntry<T> > list;
vector< ListEntry<T>* > display_list;
string search_string;
int display_max_rows;
int max_item_width;
};
/*
* Material Choice Screen
*/
class ViewscreenChooseMaterial : public dfhack_viewscreen
{
public:
static bool reset_list;
ViewscreenChooseMaterial();
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;
vector< ListEntry<df::dfhack_material_category> > all_masks;
int selected_column;
df::building_type btype;
void populateMasks(const bool set_defaults = false);
void populateMaterials(const bool set_defaults = false);
bool addMaterialEntry(df::dfhack_material_category &selected_category,
MaterialInfo &material, string name, const bool set_defaults);
virtual void resize(int32_t x, int32_t y);
void validateColumn();
};
bool ViewscreenChooseMaterial::reset_list = false;
ViewscreenChooseMaterial::ViewscreenChooseMaterial()
{
selected_column = 0;
masks_column.title = "Type";
masks_column.multiselect = true;
masks_column.left_margin = 2;
materials_column.left_margin = MAX_MASK + 3;
materials_column.title = "Material";
masks_column.changeHighlight(0);
vector<string> raw_masks;
df::dfhack_material_category full_mat_mask, curr_mat_mask;
full_mat_mask.whole = -1;
curr_mat_mask.whole = 1;
bitfield_to_string(&raw_masks, full_mat_mask);
for (int i = 0; i < raw_masks.size(); i++)
{
if (raw_masks[i][0] == '?')
break;
all_masks.push_back(ListEntry<df::dfhack_material_category>(pad_string(raw_masks[i], MAX_MASK, false), curr_mat_mask));
curr_mat_mask.whole <<= 1;
}
populateMasks();
populateMaterials();
masks_column.selectDefaultEntry();
materials_column.selectDefaultEntry();
materials_column.changeHighlight(0);
}
void ViewscreenChooseMaterial::populateMasks(const bool set_defaults /*= false */)
{
masks_column.clear();
for (vector< ListEntry<df::dfhack_material_category> >::iterator it = all_masks.begin(); it != all_masks.end(); it++)
{
auto entry = *it;
if (set_defaults)
{
//TODO
//if (cv->mat_mask.whole & entry.elem.whole)
//entry.selected = true;
}
masks_column.add(entry);
}
masks_column.sort();
}
void ViewscreenChooseMaterial::populateMaterials(const bool set_defaults /*= false */)
{
materials_column.clear();
df::dfhack_material_category selected_category;
vector<df::dfhack_material_category *> selected_materials = masks_column.getSelectedElems();
if (selected_materials.size() == 1)
selected_category = *selected_materials[0];
else if (selected_materials.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(), set_defaults);
}
}
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(), set_defaults);
}
materials_column.sort();
}
bool ViewscreenChooseMaterial::addMaterialEntry(df::dfhack_material_category &selected_category, MaterialInfo &material,
string name, const bool set_defaults)
{
bool selected = false;
if (!selected_category.whole || material.matches(selected_category))
{
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (set_defaults)
{
/*if (cv->material.matches(material))
{
entry.selected = true;
selected = true;
}*/
//TODO
}
materials_column.add(entry);
}
return selected;
}
void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
{
bool key_processed;
switch (selected_column)
{
case 0:
key_processed = masks_column.feed(input);
if (input->count(interface_key::SELECT))
populateMaterials(false);
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;
}
else if (input->count(interface_key::SEC_SELECT))
{
df::dfhack_material_category mat_mask;
vector<df::dfhack_material_category *> selected_masks = masks_column.getSelectedElems();
for (vector<df::dfhack_material_category *>::iterator it = selected_masks.begin(); it != selected_masks.end(); it++)
{
mat_mask.whole |= (*it)->whole;
}
MaterialInfo *selected_material = materials_column.getFirstSelectedElem();
MaterialInfo material = (selected_material) ? *selected_material : MaterialInfo();
//TODO
reset_list = true;
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, "Save", "Shift-Enter");
x += 3;
OutputHotkeyString(x, y, "Cancel", "Esc");
}
void ViewscreenChooseMaterial::validateColumn()
{
set_to_limit(selected_column, 1);
}
void ViewscreenChooseMaterial::resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
masks_column.resize();
materials_column.resize();
}
// START Planning
class PlannedBuilding
{
public:
PlannedBuilding(df::building *building) : min_quality(item_quality::Ordinary)
{
this->building = building;
pos = df::coord(building->centerx, building->centery, building->z);
}
df::building_type getType()
{
return building->getType();
}
bool assignClosestItem(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 pos = (*item_iter)->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))
{
items_vector->erase(closest_item);
return true;
}
return false;
}
bool 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;
auto job = building->jobs[0];
delete job->job_items[0];
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 isValid()
{
return building && Buildings::findAtTile(pos) == building;
}
bool isCurrentlySelectedBuilding()
{
return building == world->selected_building;
}
private:
df::building *building;
PersistentDataItem config;
df::coord pos;
vector<MaterialInfo> materials;
df::dfhack_material_category mat_mask;
item_quality::item_quality min_quality;
};
class Planner
{
public:
bool isPlanableBuilding(const df::building_type type) const
{
return item_for_building_type.find(type) != item_for_building_type.end();
}
void reset()
{
planned_buildings.clear();
for(auto iter = world->buildings.all.begin(); iter != world->buildings.all.end(); iter++)
{
auto bld = *iter;
if (isPlanableBuilding(bld->getType()))
{
if (bld->jobs.size() != 1)
continue;
auto job = bld->jobs[0];
if (!job->flags.bits.suspend)
continue;
if (job->job_items.size() != 1)
continue;
if (job->job_items[0]->item_type != item_type::NONE)
continue;
addPlannedBuilding(bld);
}
}
}
void initialize()
{
vector<string> item_names;
typedef df::enum_traits<df::item_type> item_types;
int size = item_types::last_item_value - item_types::first_item_value+1;
for (size_t i = 1; i < size; i++)
{
is_relevant_item_type[(df::item_type) (i-1)] = false;
string item_name = toLower(item_types::key_table[i]);
string item_name_clean;
for (auto c = item_name.begin(); c != item_name.end(); c++)
{
if (*c == '_')
continue;
item_name_clean += *c;
}
item_names.push_back(item_name_clean);
}
typedef df::enum_traits<df::building_type> building_types;
size = building_types::last_item_value - building_types::first_item_value+1;
for (size_t i = 1; i < size; i++)
{
auto building_type = (df::building_type) (i-1);
if (building_type == building_type::Weapon || building_type == building_type::Floodgate)
continue;
string building_name = toLower(building_types::key_table[i]);
for (size_t j = 0; j < item_names.size(); j++)
{
if (building_name == item_names[j])
{
item_for_building_type[(df::building_type) (i-1)] = (df::item_type) j;
available_item_vectors[(df::item_type) j] = vector<df::item *>();
is_relevant_item_type[(df::item_type) j] = true;
}
}
}
}
void addPlannedBuilding(df::building *bld)
{
PlannedBuilding pb(bld);
planned_buildings.push_back(pb);
}
void doCycle()
{
if (planned_buildings.size() == 0)
return;
gather_available_items();
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
{
if (building_iter->isValid())
{
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))
{
++building_iter;
continue;
}
}
building_iter = planned_buildings.erase(building_iter);
}
}
bool allocatePlannedBuilding(df::building_type type)
{
coord32_t cursor;
if (!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;
}
for (auto iter = newinst->jobs.begin(); iter != newinst->jobs.end(); iter++)
{
(*iter)->flags.bits.suspend = true;
}
addPlannedBuilding(newinst);
return true;
}
bool canUnsuspendSelectedBuilding()
{
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++)
{
if (building_iter->isValid() && building_iter->isCurrentlySelectedBuilding())
{
return false;
}
}
return true;
}
private:
map<df::building_type, df::item_type> item_for_building_type;
map<df::item_type, vector<df::item *>> available_item_vectors;
map<df::item_type, bool> is_relevant_item_type; //Needed for fast check when loopin over all items
vector<PlannedBuilding> planned_buildings;
void 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(artifact1);
#undef F
std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
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.in_job ||
item->isAssignedToStockpile() ||
item->flags.bits.owned ||
item->flags.bits.in_chest)
{
continue;
}
available_item_vectors[itype].push_back(item);
}
}
};
static Planner planner;
static map<df::building_type, bool> planmode_enabled;
static bool is_planmode_enabled(df::building_type type)
{
if (planmode_enabled.find(type) == planmode_enabled.end())
{
planmode_enabled[type] = false;
}
return planmode_enabled[type];
}
#define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
static decltype(world->frame_counter) last_frame_count = 0;
if ((world->frame_counter - last_frame_count) >= DAY_TICKS/2)
{
last_frame_count = world->frame_counter;
planner.doCycle();
}
return CR_OK;
}
//START Viewscreen Hook
struct buildingplan_hook : public df::viewscreen_dwarfmodest
{
//START UI Methods
typedef df::viewscreen_dwarfmodest interpose_base;
void send_key(const df::interface_key &key)
{
set< df::interface_key > keys;
keys.insert(key);
this->feed(&keys);
}
bool isInPlannedBuildingQueryMode()
{
return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding ||
ui->main.mode == df::ui_sidebar_mode::BuildingItems) &&
!planner.canUnsuspendSelectedBuilding();
}
bool isInPlannedBuildingPlacementMode()
{
return ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector &&
ui_build_selector->stage < 2 &&
planner.isPlanableBuilding(ui_build_selector->building_type);
}
bool handle_input(set<df::interface_key> *input)
{
if (isInPlannedBuildingPlacementMode())
{
auto type = ui_build_selector->building_type;
if (input->count(interface_key::CUSTOM_P))
{
planmode_enabled[type] = !planmode_enabled[type];
if (!planmode_enabled[type])
{
send_key(interface_key::CURSOR_DOWN_Z);
send_key(interface_key::CURSOR_UP_Z);
}
return true;
}
if (is_planmode_enabled(type))
{
if (input->count(interface_key::SELECT))
{
if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(type))
{
send_key(interface_key::CURSOR_DOWN_Z);
send_key(interface_key::CURSOR_UP_Z);
}
return true;
}
}
}
else if (isInPlannedBuildingQueryMode() &&
input->count(interface_key::SUSPENDBUILDING))
{
return true; // Don't unsuspend planned buildings
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (!handle_input(input))
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
bool plannable = isInPlannedBuildingPlacementMode();
if (plannable && is_planmode_enabled(ui_build_selector->building_type))
{
if (ui_build_selector->stage < 1)
{
// No materials but turn on cursor
ui_build_selector->stage = 1;
}
for (auto iter = ui_build_selector->errors.begin(); iter != ui_build_selector->errors.end();)
{
//FIXME Hide bags
if (((*iter)->find("Needs") != string::npos && **iter != "Needs adjacent wall") ||
(*iter)->find("No access") != string::npos)
{
iter = ui_build_selector->errors.erase(iter);
}
else
{
++iter;
}
}
}
INTERPOSE_NEXT(render)();
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
if (plannable)
{
int y = 23;
OutputToggleString(x, y, "Planning Mode", "p", is_planmode_enabled(ui_build_selector->building_type), true, left_margin);
if (is_planmode_enabled(ui_build_selector->building_type))
{
//OutputHotkeyString(x, y, "Material Filter", "m", true, left_margin);
}
}
else if (isInPlannedBuildingQueryMode())
{
// Hide suspend toggle option
int y = 20;
Screen::Pen pen(' ', COLOR_BLACK);
Screen::fillRect(pen, x, y, dims.menu_x2, y);
}
}
};
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())
{
out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !INTERPOSE_HOOK(buildingplan_hook, feed).apply() || !INTERPOSE_HOOK(buildingplan_hook, render).apply())
out.printerr("Could not insert buildingplan hooks!\n");
commands.push_back(
PluginCommand(
"buildingplan", "Place furniture before it's built",
buildingplan_cmd, false, ""));
planner.initialize();
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
planner.reset();
break;
default:
break;
}
return CR_OK;
}