2020-10-04 21:05:08 -06:00
|
|
|
#include <unordered_map>
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
#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"
|
2020-10-04 21:05:08 -06:00
|
|
|
#include "buildingplan-lib.h"
|
2013-01-07 01:17:38 -07:00
|
|
|
|
|
|
|
DFHACK_PLUGIN("buildingplan");
|
2020-09-08 01:17:56 -06:00
|
|
|
#define PLUGIN_VERSION 0.15
|
2014-12-02 19:44:20 -07:00
|
|
|
REQUIRE_GLOBAL(ui);
|
|
|
|
REQUIRE_GLOBAL(ui_build_selector);
|
|
|
|
REQUIRE_GLOBAL(world);
|
2013-02-01 06:22:06 -07:00
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
#define MAX_MASK 10
|
|
|
|
#define MAX_MATERIAL 21
|
2013-01-18 23:20:37 -07:00
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
using namespace DFHack;
|
|
|
|
using namespace df::enums;
|
2013-10-02 09:55:48 -06:00
|
|
|
|
2020-09-08 18:34:11 -06:00
|
|
|
bool quickfort_mode = false;
|
|
|
|
bool in_dummy_screen = false;
|
2020-10-04 21:05:08 -06:00
|
|
|
std::unordered_map<BuildingTypeKey, bool, BuildingTypeKeyHash> planmode_enabled;
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
class ViewscreenChooseMaterial : public dfhack_viewscreen
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
public:
|
2020-10-04 21:05:08 -06:00
|
|
|
ViewscreenChooseMaterial(ItemFilter &filter);
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
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;
|
2020-10-04 21:05:08 -06:00
|
|
|
ItemFilter &filter;
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
void addMaskEntry(df::dfhack_material_category &mask, const std::string &text)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
auto entry = ListEntry<df::dfhack_material_category>(pad_string(text, MAX_MASK, false), mask);
|
2020-10-04 21:05:08 -06:00
|
|
|
if (filter.matches(mask))
|
2020-09-08 01:17:56 -06:00
|
|
|
entry.selected = true;
|
|
|
|
|
|
|
|
masks_column.add(entry);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
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);
|
2020-10-04 21:05:08 -06:00
|
|
|
if (filter.matches(material))
|
2020-09-08 01:17:56 -06:00
|
|
|
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);
|
2020-10-04 21:05:08 -06:00
|
|
|
if (filter.matches(material))
|
2020-09-08 01:17:56 -06:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
const DFHack::MaterialInfo &material_info_identity_fn(const DFHack::MaterialInfo &m) { return m; }
|
2020-09-08 01:17:56 -06:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter &filter)
|
|
|
|
: filter(filter)
|
2020-09-08 01:17:56 -06:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
|
|
|
masks_column.changeHighlight(0);
|
|
|
|
|
|
|
|
populateMasks();
|
|
|
|
populateMaterials();
|
|
|
|
|
|
|
|
masks_column.selectDefaultEntry();
|
|
|
|
materials_column.selectDefaultEntry();
|
|
|
|
materials_column.changeHighlight(0);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
bool key_processed = false;
|
|
|
|
switch (selected_column)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-09-08 01:17:56 -06:00
|
|
|
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;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
if (key_processed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (input->count(interface_key::LEAVESCREEN))
|
|
|
|
{
|
|
|
|
input->clear();
|
|
|
|
Screen::dismiss(this);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_C))
|
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
filter.clear();
|
2020-09-08 01:17:56 -06:00
|
|
|
masks_column.clearSelection();
|
|
|
|
materials_column.clearSelection();
|
|
|
|
populateMaterials();
|
|
|
|
}
|
|
|
|
else if (input->count(interface_key::SEC_SELECT))
|
|
|
|
{
|
|
|
|
// Convert list selections to material filters
|
2020-10-04 21:05:08 -06:00
|
|
|
filter.clearMaterialMask();
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
// Category masks
|
|
|
|
auto masks = masks_column.getSelectedElems();
|
|
|
|
for (auto it = masks.begin(); it != masks.end(); ++it)
|
2020-10-04 21:05:08 -06:00
|
|
|
filter.addMaterialMask(it->whole);
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
// Specific materials
|
|
|
|
auto materials = materials_column.getSelectedElems();
|
2020-10-04 21:05:08 -06:00
|
|
|
std::vector<DFHack::MaterialInfo> materialInfos;
|
|
|
|
transform_(materials, materialInfos, material_info_identity_fn);
|
|
|
|
filter.setMaterials(materialInfos);
|
2020-09-08 01:17:56 -06:00
|
|
|
|
|
|
|
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);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//START Viewscreen Hook
|
2020-10-04 21:05:08 -06:00
|
|
|
static bool is_planmode_enabled(BuildingTypeKey key)
|
2020-09-08 01:17:56 -06:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
return planmode_enabled[key] || quickfort_mode;
|
2020-09-08 01:17:56 -06:00
|
|
|
}
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
struct buildingplan_query_hook : public df::viewscreen_dwarfmodest
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
|
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
|
|
|
|
bool isInPlannedBuildingQueryMode()
|
|
|
|
{
|
2015-02-14 20:53:06 -07:00
|
|
|
return (ui->main.mode == df::ui_sidebar_mode::QueryBuilding ||
|
2013-01-07 01:17:38 -07:00
|
|
|
ui->main.mode == df::ui_sidebar_mode::BuildingItems) &&
|
2020-10-04 21:05:08 -06:00
|
|
|
planner.getPlannedBuilding(world->selected_building);
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
bool handleInput(set<df::interface_key> *input)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
if (!isInPlannedBuildingQueryMode())
|
|
|
|
return false;
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
if (input->count(interface_key::SUSPENDBUILDING))
|
|
|
|
return true; // Don't unsuspend planned buildings
|
|
|
|
if (input->count(interface_key::DESTROYBUILDING))
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
// remove persistent data
|
|
|
|
planner.getPlannedBuilding(world->selected_building)->remove();
|
|
|
|
// still allow the building to be removed
|
|
|
|
return false;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
return true;
|
|
|
|
}
|
2014-06-14 05:50:47 -06:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
|
|
|
{
|
|
|
|
if (!handleInput(input))
|
|
|
|
INTERPOSE_NEXT(feed)(input);
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
INTERPOSE_NEXT(render)();
|
|
|
|
|
|
|
|
if (!isInPlannedBuildingQueryMode())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Hide suspend toggle option
|
|
|
|
auto dims = Gui::getDwarfmodeViewDims();
|
|
|
|
int left_margin = dims.menu_x1 + 1;
|
|
|
|
int x = left_margin;
|
|
|
|
int y = 20;
|
|
|
|
Screen::Pen pen(' ', COLOR_BLACK);
|
|
|
|
Screen::fillRect(pen, x, y, dims.menu_x2, y);
|
|
|
|
|
|
|
|
// all current buildings only have one filter
|
|
|
|
auto & filter = planner.getPlannedBuilding(world->selected_building)->getFilters()[0];
|
|
|
|
y = 24;
|
|
|
|
OutputString(COLOR_WHITE, x, y, "Planned Building Filter", true, left_margin);
|
|
|
|
++y;
|
|
|
|
OutputString(COLOR_BROWN, x, y, "Min Quality: ", false, left_margin);
|
|
|
|
OutputString(COLOR_BLUE, x, y, filter.getMinQuality(), true, left_margin);
|
|
|
|
OutputString(COLOR_BROWN, x, y, "Max Quality: ", false, left_margin);
|
|
|
|
OutputString(COLOR_BLUE, x, y, filter.getMaxQuality(), true, left_margin);
|
|
|
|
|
|
|
|
if (filter.getDecoratedOnly())
|
|
|
|
OutputString(COLOR_BLUE, x, y, "Decorated Only", true, left_margin);
|
|
|
|
|
|
|
|
OutputString(COLOR_BROWN, x, y, "Materials:", true, left_margin);
|
|
|
|
auto filters = filter.getMaterials();
|
|
|
|
for (auto it = filters.begin(); it != filters.end(); ++it)
|
|
|
|
OutputString(COLOR_BLUE, x, y, "*" + *it, true, left_margin);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct buildingplan_place_hook : public df::viewscreen_dwarfmodest
|
|
|
|
{
|
|
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
|
|
|
|
bool isInPlannedBuildingPlacementMode()
|
|
|
|
{
|
|
|
|
return ui->main.mode == ui_sidebar_mode::Build &&
|
|
|
|
df::global::ui_build_selector &&
|
|
|
|
df::global::ui_build_selector->stage < 2 &&
|
|
|
|
planner.isPlannableBuilding(toBuildingTypeKey(ui_build_selector));
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
|
|
|
|
2013-01-18 23:20:37 -07:00
|
|
|
bool handleInput(set<df::interface_key> *input)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
if (!isInPlannedBuildingPlacementMode())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (in_dummy_screen)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)
|
|
|
|
|| input->count(interface_key::LEAVESCREEN))
|
2014-05-04 04:23:10 -06:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
in_dummy_screen = false;
|
|
|
|
// pass LEAVESCREEN up to parent view
|
|
|
|
input->clear();
|
|
|
|
input->insert(interface_key::LEAVESCREEN);
|
|
|
|
return false;
|
2014-05-04 04:23:10 -06:00
|
|
|
}
|
2020-10-04 21:05:08 -06:00
|
|
|
return true;
|
|
|
|
}
|
2015-02-14 20:53:06 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
BuildingTypeKey key = toBuildingTypeKey(ui_build_selector);
|
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_P))
|
|
|
|
{
|
|
|
|
planmode_enabled[key] = !planmode_enabled[key];
|
|
|
|
if (!is_planmode_enabled(key))
|
|
|
|
Gui::refreshSidebar();
|
|
|
|
return true;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
2020-10-04 21:05:08 -06:00
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_F))
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
quickfort_mode = !quickfort_mode;
|
|
|
|
return true;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
2020-10-04 21:05:08 -06:00
|
|
|
|
|
|
|
if (!is_planmode_enabled(key))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (input->count(interface_key::SELECT))
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
if (ui_build_selector->errors.size() == 0 && planner.allocatePlannedBuilding(key))
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
Gui::refreshSidebar();
|
|
|
|
if (quickfort_mode)
|
|
|
|
in_dummy_screen = true;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
2020-10-04 21:05:08 -06:00
|
|
|
return true;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
// all current buildings only have one filter
|
|
|
|
auto filter = planner.getItemFilters(key).rbegin();
|
|
|
|
if (input->count(interface_key::CUSTOM_SHIFT_M))
|
|
|
|
Screen::show(dts::make_unique<ViewscreenChooseMaterial>(*filter), plugin_self);
|
|
|
|
else if (input->count(interface_key::CUSTOM_Q))
|
|
|
|
filter->decMinQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_W))
|
|
|
|
filter->incMinQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_Q))
|
|
|
|
filter->decMaxQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_W))
|
|
|
|
filter->incMaxQuality();
|
|
|
|
else if (input->count(interface_key::CUSTOM_SHIFT_D))
|
|
|
|
filter->toggleDecoratedOnly();
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
return true;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
|
|
|
{
|
2013-01-18 23:20:37 -07:00
|
|
|
if (!handleInput(input))
|
2013-01-07 01:17:38 -07:00
|
|
|
INTERPOSE_NEXT(feed)(input);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
|
|
|
{
|
|
|
|
bool plannable = isInPlannedBuildingPlacementMode();
|
2020-10-04 21:05:08 -06:00
|
|
|
BuildingTypeKey key = toBuildingTypeKey(ui_build_selector);
|
|
|
|
if (plannable && is_planmode_enabled(key))
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
|
|
|
if (ui_build_selector->stage < 1)
|
|
|
|
// No materials but turn on cursor
|
|
|
|
ui_build_selector->stage = 1;
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
for (auto iter = ui_build_selector->errors.begin();
|
|
|
|
iter != ui_build_selector->errors.end();)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
// FIXME Hide bags
|
|
|
|
if (((*iter)->find("Needs") != string::npos
|
|
|
|
&& **iter != "Needs adjacent wall")
|
|
|
|
|| (*iter)->find("No access") != string::npos)
|
2013-01-07 01:17:38 -07:00
|
|
|
iter = ui_build_selector->errors.erase(iter);
|
|
|
|
else
|
|
|
|
++iter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
INTERPOSE_NEXT(render)();
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
if (!plannable)
|
|
|
|
return;
|
|
|
|
|
2013-01-07 01:17:38 -07:00
|
|
|
auto dims = Gui::getDwarfmodeViewDims();
|
|
|
|
int left_margin = dims.menu_x1 + 1;
|
|
|
|
int x = left_margin;
|
2020-10-04 21:05:08 -06:00
|
|
|
|
|
|
|
if (in_dummy_screen)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
Screen::Pen pen(' ',COLOR_BLACK);
|
|
|
|
int y = dims.y1 + 1;
|
|
|
|
Screen::fillRect(pen, x, y, dims.menu_x2, y + 20);
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
++y;
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
OutputString(COLOR_BROWN, x, y,
|
|
|
|
"Placeholder for legacy Quickfort. This screen is not required for DFHack native quickfort.",
|
|
|
|
true, left_margin);
|
|
|
|
OutputString(COLOR_WHITE, x, y, "Enter, Shift-Enter or Esc", true, left_margin);
|
|
|
|
return;
|
|
|
|
}
|
2013-02-07 02:57:07 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
int y = 23;
|
2013-01-18 23:20:37 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
OutputToggleString(x, y, "Planning Mode", "P", planmode_enabled[key], true, left_margin);
|
|
|
|
OutputToggleString(x, y, "Quickfort Mode", "F", quickfort_mode, true, left_margin);
|
2013-01-18 23:20:37 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
if (!is_planmode_enabled(key))
|
|
|
|
return;
|
2013-02-07 04:04:52 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
auto filter = planner.getItemFilters(key).rbegin();
|
|
|
|
y += 2;
|
|
|
|
OutputHotkeyString(x, y, "Min Quality: ", "qw");
|
|
|
|
OutputString(COLOR_BROWN, x, y, filter->getMinQuality(), true, left_margin);
|
2013-02-07 04:04:52 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
OutputHotkeyString(x, y, "Max Quality: ", "QW");
|
|
|
|
OutputString(COLOR_BROWN, x, y, filter->getMaxQuality(), true, left_margin);
|
2018-05-20 15:25:59 -06:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
OutputToggleString(x, y, "Decorated Only", "D", filter->getDecoratedOnly(), true, left_margin);
|
2013-02-07 04:04:52 -07:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
OutputHotkeyString(x, y, "Material Filter:", "M", true, left_margin);
|
|
|
|
auto filter_descriptions = filter->getMaterials();
|
|
|
|
for (auto it = filter_descriptions.begin();
|
|
|
|
it != filter_descriptions.end(); ++it)
|
|
|
|
OutputString(COLOR_BROWN, x, y, " *" + *it, true, left_margin);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct buildingplan_room_hook : public df::viewscreen_dwarfmodest
|
|
|
|
{
|
|
|
|
typedef df::viewscreen_dwarfmodest interpose_base;
|
|
|
|
|
|
|
|
std::vector<Units::NoblePosition> getNoblePositionOfSelectedBuildingOwner()
|
|
|
|
{
|
|
|
|
std::vector<Units::NoblePosition> np;
|
|
|
|
if (ui->main.mode != df::ui_sidebar_mode::QueryBuilding ||
|
|
|
|
!world->selected_building ||
|
|
|
|
!world->selected_building->owner)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
return np;
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
2020-10-04 21:05:08 -06:00
|
|
|
|
|
|
|
switch (world->selected_building->getType())
|
2014-06-14 05:50:47 -06:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
case building_type::Bed:
|
|
|
|
case building_type::Chair:
|
|
|
|
case building_type::Table:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return np;
|
2014-06-14 05:50:47 -06:00
|
|
|
}
|
2020-10-04 21:05:08 -06:00
|
|
|
|
|
|
|
return getUniqueNoblePositions(world->selected_building->owner);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isInNobleRoomQueryMode()
|
|
|
|
{
|
|
|
|
if (getNoblePositionOfSelectedBuildingOwner().size() > 0)
|
|
|
|
return canReserveRoom(world->selected_building);
|
2013-02-07 04:04:52 -07:00
|
|
|
else
|
2020-10-04 21:05:08 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool handleInput(set<df::interface_key> *input)
|
|
|
|
{
|
|
|
|
if (!isInNobleRoomQueryMode())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (Gui::inRenameBuilding())
|
|
|
|
return false;
|
|
|
|
auto np = getNoblePositionOfSelectedBuildingOwner();
|
|
|
|
df::interface_key last_token = get_string_key(input);
|
|
|
|
if (last_token >= interface_key::STRING_A048
|
|
|
|
&& last_token <= interface_key::STRING_A058)
|
|
|
|
{
|
|
|
|
size_t selection = last_token - interface_key::STRING_A048;
|
|
|
|
if (np.size() < selection)
|
|
|
|
return false;
|
|
|
|
roomMonitor.toggleRoomForPosition(world->selected_building->id, np.at(selection-1).position->code);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
|
|
|
{
|
|
|
|
if (!handleInput(input))
|
|
|
|
INTERPOSE_NEXT(feed)(input);
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
|
|
|
{
|
|
|
|
INTERPOSE_NEXT(render)();
|
|
|
|
|
|
|
|
if (!isInNobleRoomQueryMode())
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto np = getNoblePositionOfSelectedBuildingOwner();
|
|
|
|
auto dims = Gui::getDwarfmodeViewDims();
|
|
|
|
int left_margin = dims.menu_x1 + 1;
|
|
|
|
int x = left_margin;
|
|
|
|
int y = 24;
|
|
|
|
OutputString(COLOR_BROWN, x, y, "DFHack", true, left_margin);
|
|
|
|
OutputString(COLOR_WHITE, x, y, "Auto-allocate to:", true, left_margin);
|
|
|
|
for (size_t i = 0; i < np.size() && i < 9; i++)
|
2013-02-07 04:04:52 -07:00
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
bool enabled =
|
|
|
|
roomMonitor.getReservedNobleCode(world->selected_building->id)
|
|
|
|
== np[i].position->code;
|
|
|
|
OutputToggleString(x, y, np[i].position->name[0].c_str(),
|
|
|
|
int_to_string(i+1).c_str(), enabled, true, left_margin);
|
2013-02-07 04:04:52 -07:00
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, feed);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, feed);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, feed);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_query_hook, render);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_place_hook, render);
|
|
|
|
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_room_hook, render);
|
2013-01-07 01:17:38 -07:00
|
|
|
|
|
|
|
static command_result buildingplan_cmd(color_ostream &out, vector <string> & parameters)
|
|
|
|
{
|
|
|
|
if (!parameters.empty())
|
|
|
|
{
|
2013-03-09 20:14:00 -07:00
|
|
|
if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v')
|
|
|
|
{
|
|
|
|
out << "Building Plan" << endl << "Version: " << PLUGIN_VERSION << endl;
|
|
|
|
}
|
|
|
|
else if (parameters.size() == 2 && toLower(parameters[0]) == "debug")
|
|
|
|
{
|
|
|
|
show_debugging = (toLower(parameters[1]) == "on");
|
|
|
|
out << "Debugging " << ((show_debugging) ? "enabled" : "disabled") << endl;
|
2015-02-14 20:53:06 -07:00
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2013-09-30 03:19:51 -06:00
|
|
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2013-09-30 03:19:51 -06:00
|
|
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
|
2013-01-07 01:17:38 -07:00
|
|
|
{
|
2013-09-30 03:19:51 -06:00
|
|
|
if (!gps)
|
|
|
|
return CR_FAILURE;
|
|
|
|
|
|
|
|
if (enable != is_enabled)
|
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
planner.reset();
|
|
|
|
|
|
|
|
if (!INTERPOSE_HOOK(buildingplan_query_hook, feed).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_place_hook, feed).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_room_hook, feed).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_query_hook, render).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_place_hook, render).apply(enable) ||
|
|
|
|
!INTERPOSE_HOOK(buildingplan_room_hook, render).apply(enable))
|
2013-09-30 03:19:51 -06:00
|
|
|
return CR_FAILURE;
|
|
|
|
|
|
|
|
is_enabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
2013-01-07 01:17:38 -07:00
|
|
|
|
2013-09-30 03:19:51 -06:00
|
|
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
|
|
|
{
|
2013-01-07 01:17:38 -07:00
|
|
|
commands.push_back(
|
|
|
|
PluginCommand(
|
2020-10-04 21:05:08 -06:00
|
|
|
"buildingplan", "Plan building construction before you have materials",
|
2013-10-30 14:58:14 -06:00
|
|
|
buildingplan_cmd, false, "Run 'buildingplan debug [on|off]' to toggle debugging, or 'buildingplan version' to query the plugin version."));
|
2013-01-07 01:17:38 -07:00
|
|
|
planner.initialize();
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
|
|
|
|
{
|
|
|
|
switch (event) {
|
|
|
|
case SC_MAP_LOADED:
|
2020-10-04 21:05:08 -06:00
|
|
|
planner.reset();
|
2014-06-14 05:50:47 -06:00
|
|
|
roomMonitor.reset(out);
|
2013-01-07 01:17:38 -07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
2013-01-29 00:44:56 -07:00
|
|
|
}
|
2020-08-12 16:12:53 -06:00
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
static bool cycle_requested = false;
|
|
|
|
|
2020-09-08 01:17:56 -06:00
|
|
|
#define DAY_TICKS 1200
|
|
|
|
DFhackCExport command_result plugin_onupdate(color_ostream &)
|
|
|
|
{
|
2020-10-04 21:05:08 -06:00
|
|
|
if (Maps::IsValid() && !World::ReadPauseState()
|
|
|
|
&& (cycle_requested || world->frame_counter % (DAY_TICKS/2) == 0))
|
2020-09-08 01:17:56 -06:00
|
|
|
{
|
|
|
|
planner.doCycle();
|
|
|
|
roomMonitor.doCycle();
|
2020-10-04 21:05:08 -06:00
|
|
|
cycle_requested = false;
|
2020-09-08 01:17:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
DFhackCExport command_result plugin_shutdown(color_ostream &)
|
|
|
|
{
|
|
|
|
return CR_OK;
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:12:53 -06:00
|
|
|
// Lua API section
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
static bool isPlannableBuilding(df::building_type type,
|
|
|
|
int16_t subtype,
|
|
|
|
int32_t custom) {
|
|
|
|
return planner.isPlannableBuilding(
|
|
|
|
toBuildingTypeKey(type, subtype, custom));
|
2020-08-12 16:12:53 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static void addPlannedBuilding(df::building *bld) {
|
|
|
|
planner.addPlannedBuilding(bld);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doCycle() {
|
|
|
|
planner.doCycle();
|
|
|
|
}
|
|
|
|
|
2020-10-04 21:05:08 -06:00
|
|
|
static void scheduleCycle() {
|
|
|
|
cycle_requested = true;
|
|
|
|
}
|
|
|
|
|
2020-08-12 16:12:53 -06:00
|
|
|
DFHACK_PLUGIN_LUA_FUNCTIONS {
|
|
|
|
DFHACK_LUA_FUNCTION(isPlannableBuilding),
|
|
|
|
DFHACK_LUA_FUNCTION(addPlannedBuilding),
|
|
|
|
DFHACK_LUA_FUNCTION(doCycle),
|
2020-10-04 21:05:08 -06:00
|
|
|
DFHACK_LUA_FUNCTION(scheduleCycle),
|
2020-08-12 16:12:53 -06:00
|
|
|
DFHACK_LUA_END
|
|
|
|
};
|