dfhack/plugins/automaterial.cpp

1341 lines
43 KiB
C++

// Auto Material Select
#include <map>
#include <string>
#include <vector>
#include "Core.h"
#include "LuaTools.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "Debug.h"
#include "MiscUtils.h"
#include "TileTypes.h"
#include "df/build_req_choice_genst.h"
#include "df/build_req_choice_specst.h"
#include "df/construction_type.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/job_item.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Items.h"
#include "modules/Constructions.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/MapCache.h"
#include "uicommon.h"
using namespace std;
using std::map;
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("automaterial");
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_build_selector);
namespace DFHack {
DBG_DECLARE(automaterial,log,DebugCategory::LINFO);
}
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;
}
};
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
void AMOutputToggleString(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);
}
//START UI Functions
static enum t_box_select_mode {SELECT_FIRST, SELECT_SECOND, SELECT_MATERIALS, AUTOSELECT_MATERIALS} box_select_mode = SELECT_FIRST;
static coord32_t box_first, box_second;
static bool box_select_enabled = false;
static bool show_box_selection = true;
static bool hollow_selection = false;
static deque<df::item*> box_select_materials;
#define SELECTION_IGNORE_TICKS 1
static int ignore_selection = SELECTION_IGNORE_TICKS;
static map<int16_t, MaterialDescriptor> last_used_material;
static map<int16_t, MaterialDescriptor> last_moved_material;
static map< int16_t, vector<MaterialDescriptor> > preferred_materials;
static map< int16_t, df::interface_key > hotkeys;
static bool last_used_moved = false;
static bool auto_choose_materials = true;
static bool revert_to_last_used_type = false;
static bool allow_future_placement = false;
static inline bool in_material_choice_stage()
{
return Gui::build_selector_hotkey(Core::getTopViewscreen()) &&
ui_build_selector->building_type == df::building_type::Construction &&
ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector->stage == 2;
}
static inline bool in_placement_stage()
{
return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) &&
ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector &&
ui_build_selector->building_type == df::building_type::Construction &&
ui_build_selector->stage == 1;
}
static inline bool in_type_choice_stage()
{
return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) &&
ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector &&
ui_build_selector->building_type < 0;
}
static inline vector<MaterialDescriptor> &get_curr_constr_prefs()
{
if (preferred_materials.find(ui_build_selector->building_subtype) == preferred_materials.end())
preferred_materials[ui_build_selector->building_subtype] = vector<MaterialDescriptor>();
return preferred_materials[ui_build_selector->building_subtype];
}
static inline MaterialDescriptor &get_last_used_material()
{
if (last_used_material.find(ui_build_selector->building_subtype) == last_used_material.end())
last_used_material[ui_build_selector->building_subtype] = MaterialDescriptor();
return last_used_material[ui_build_selector->building_subtype];
}
static void set_last_used_material(const MaterialDescriptor &matetial)
{
last_used_material[ui_build_selector->building_subtype] = matetial;
}
static MaterialDescriptor &get_last_moved_material()
{
if (last_moved_material.find(ui_build_selector->building_subtype) == last_moved_material.end())
last_moved_material[ui_build_selector->building_subtype] = MaterialDescriptor();
return last_moved_material[ui_build_selector->building_subtype];
}
static void set_last_moved_material(const MaterialDescriptor &matetial)
{
last_moved_material[ui_build_selector->building_subtype] = matetial;
}
static MaterialDescriptor get_material_in_list(size_t i)
{
MaterialDescriptor result;
result.valid = false;
if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, ui_build_selector->choices[i]))
{
result.item_type = gen->item_type;
result.item_subtype = gen->item_subtype;
result.type = gen->mat_type;
result.index = gen->mat_index;
result.valid = true;
}
else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i]))
{
result.item_type = spec->candidate->getType();
result.item_subtype = spec->candidate->getSubtype();
result.type = spec->candidate->getActualMaterial();
result.index = spec->candidate->getActualMaterialIndex();
result.valid = true;
}
return result;
}
static bool is_material_in_autoselect(size_t &i, MaterialDescriptor &material)
{
for (i = 0; i < get_curr_constr_prefs().size(); i++)
{
if (get_curr_constr_prefs()[i].matches(material))
return true;
}
return false;
}
static bool is_material_in_list(size_t &i, MaterialDescriptor &material)
{
const size_t size = ui_build_selector->choices.size(); //Just because material list could be very big
for (i = 0; i < size; i++)
{
if (get_material_in_list(i).matches(material))
return true;
}
return false;
}
static bool move_material_to_top(MaterialDescriptor &material)
{
size_t i;
if (is_material_in_list(i, material))
{
auto sel_item = ui_build_selector->choices[i];
ui_build_selector->choices.erase(ui_build_selector->choices.begin() + i);
ui_build_selector->choices.insert(ui_build_selector->choices.begin(), sel_item);
ui_build_selector->sel_index = 0;
set_last_moved_material(material);
return true;
}
set_last_moved_material(MaterialDescriptor());
return false;
}
static bool check_autoselect(MaterialDescriptor &material, bool toggle)
{
size_t idx;
if (is_material_in_autoselect(idx, material))
{
if (toggle)
vector_erase_at(get_curr_constr_prefs(), idx);
return true;
}
else
{
if (toggle)
get_curr_constr_prefs().push_back(material);
return false;
}
}
static void cancel_box_selection()
{
if (box_select_mode == SELECT_FIRST)
return;
box_select_mode = SELECT_FIRST;
box_select_materials.clear();
if (!show_box_selection)
Gui::setDesignationCoords(-1, -1, -1);
}
//END UI Functions
//START Building and Verification
struct building_site
{
df::coord pos;
bool in_open_air;
building_site(df::coord pos, bool in_open_air)
{
this->pos = pos;
this->in_open_air = in_open_air;
}
building_site() {}
};
static deque<building_site> valid_building_sites;
static deque<building_site> open_air_sites;
static building_site anchor;
static bool is_orthogonal_to_pending_construction(building_site &site)
{
for (deque<building_site>::iterator it = valid_building_sites.begin(); it != valid_building_sites.end(); it++)
{
if ((it->pos.x == site.pos.x && abs(it->pos.y - site.pos.y) == 1) || (it->pos.y == site.pos.y && abs(it->pos.x - site.pos.x) == 1))
{
site.in_open_air = true;
return true;
}
}
return false;
}
static df::building_constructionst *get_construction_on_tile(const df::coord &pos)
{
auto current = Buildings::findAtTile(pos);
if (current)
return strict_virtual_cast<df::building_constructionst>(current);
return NULL;
}
static df::tiletype *read_tile_shapes(const df::coord &pos, df::tiletype_shape &shape, df::tiletype_shape_basic &shape_basic)
{
if (!Maps::isValidTilePos(pos))
return NULL;
auto ttype = Maps::getTileType(pos);
if (!ttype)
return NULL;
shape = tileShape(*ttype);
shape_basic = tileShapeBasic(shape);
return ttype;
}
static bool is_valid_building_site(building_site &site, bool orthogonal_check, bool check_placed_constructions, bool in_future_placement_mode)
{
df::tiletype_shape shape;
df::tiletype_shape_basic shape_basic;
auto ttype = read_tile_shapes(site.pos, shape, shape_basic);
if (!ttype)
return false;
if (shape_basic == tiletype_shape_basic::Open)
{
if (orthogonal_check)
{
// Check if this is a valid tile to have a construction placed orthogonally to it
if (!in_future_placement_mode)
return false;
df::building_constructionst *cons = get_construction_on_tile(site.pos);
if (cons && cons->type == construction_type::Floor)
{
site.in_open_air = true;
return true;
}
return false;
}
// Stairs can be placed in open space, if they can connect to other stairs
df::tiletype_shape shape_s;
df::tiletype_shape_basic shape_basic_s;
if (ui_build_selector->building_subtype == construction_type::DownStair ||
ui_build_selector->building_subtype == construction_type::UpDownStair)
{
df::coord below(site.pos.x, site.pos.y, site.pos.z - 1);
auto ttype_s = read_tile_shapes(below, shape_s, shape_basic_s);
if (ttype_s)
{
if (shape_s == tiletype_shape::STAIR_UP || shape_s == tiletype_shape::STAIR_UPDOWN)
return true;
}
}
if (ui_build_selector->building_subtype == construction_type::UpStair ||
ui_build_selector->building_subtype == construction_type::UpDownStair)
{
df::coord above(site.pos.x, site.pos.y, site.pos.z + 1);
auto ttype_s = read_tile_shapes(above, shape_s, shape_basic_s);
if (ttype_s)
{
if (shape_s == tiletype_shape::STAIR_DOWN || shape_s == tiletype_shape::STAIR_UPDOWN)
return true;
}
}
// Check if there is a valid tile orthogonally adjacent
bool valid_orthogonal_tile_found = false;
df::coord orthagonal_pos;
orthagonal_pos.z = site.pos.z;
for (orthagonal_pos.x = site.pos.x-1; orthagonal_pos.x <= site.pos.x+1 && !valid_orthogonal_tile_found; orthagonal_pos.x++)
{
for (orthagonal_pos.y = site.pos.y-1; orthagonal_pos.y <= site.pos.y+1; orthagonal_pos.y++)
{
if ((site.pos.x == orthagonal_pos.x) == (site.pos.y == orthagonal_pos.y))
continue;
building_site orthogonal_site(orthagonal_pos, false);
if (is_valid_building_site(orthogonal_site, true, check_placed_constructions, in_future_placement_mode))
{
valid_orthogonal_tile_found = true;
if (orthogonal_site.in_open_air)
site.in_open_air = true;
break;
}
}
}
if (!(valid_orthogonal_tile_found || (check_placed_constructions && is_orthogonal_to_pending_construction(site))))
{
site.in_open_air = true;
return false;
}
}
else if (orthogonal_check)
{
if (shape != tiletype_shape::RAMP &&
shape_basic != tiletype_shape_basic::Floor &&
shape_basic != tiletype_shape_basic::Stair)
return false;
}
else
{
auto material = tileMaterial(*ttype);
if (shape == tiletype_shape::RAMP)
{
if (material == tiletype_material::CONSTRUCTION)
return false;
}
else
{
if (shape != tiletype_shape::STAIR_DOWN && shape_basic != tiletype_shape_basic::Floor)
return false;
// Can build on top of a wall, but not on other construction
auto construction = Constructions::findAtTile(site.pos);
if (construction)
{
if (construction->flags.bits.top_of_wall==0)
return false;
}
if (material == tiletype_material::FIRE ||
material == tiletype_material::POOL ||
material == tiletype_material::BROOK ||
material == tiletype_material::RIVER ||
material == tiletype_material::MAGMA ||
material == tiletype_material::DRIFTWOOD ||
material == tiletype_material::CAMPFIRE
)
return false;
}
}
if (orthogonal_check)
return true;
auto designation = Maps::getTileDesignation(site.pos);
if (designation->bits.flow_size > 2)
return false;
auto current = Buildings::findAtTile(site.pos);
if (current)
return false;
df::coord2d size(1,1);
return Buildings::checkFreeTiles(site.pos, size, NULL, false, false);
}
static bool find_anchor_in_spiral(const df::coord &start)
{
bool found = false;
for (anchor.pos.z = start.z; anchor.pos.z > start.z - 4; anchor.pos.z--)
{
int x, y, dx, dy;
x = y = dx = 0;
dy = -1;
const int side = 11;
const int maxI = side*side;
for (int i = 0; i < maxI; i++)
{
if (-side/2 < x && x <= side/2 && -side/2 < y && y <= side/2)
{
anchor.pos.x = start.x + x;
anchor.pos.y = start.y + y;
if (is_valid_building_site(anchor, false, false, false))
{
found = true;
break;
}
}
if ((x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1-y)))
{
int tmp = dx;
dx = -dy;
dy = tmp;
}
x += dx;
y += dy;
}
if (found)
break;
}
return found;
}
static bool find_valid_building_sites(bool in_future_placement_mode, bool use_buildingplan)
{
valid_building_sites.clear();
open_air_sites.clear();
int xD = (box_second.x > box_first.x) ? 1 : -1;
int yD = (box_second.y > box_first.y) ? 1 : -1;
for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD)
{
for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD)
{
if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y))
continue;
building_site site(df::coord(xB, yB, box_second.z), false);
// if we're using buildingplan, it will take care of filtering out bad tiles
if (use_buildingplan || is_valid_building_site(site, false, true, in_future_placement_mode))
valid_building_sites.push_back(site);
else if (site.in_open_air)
{
if (in_future_placement_mode)
valid_building_sites.push_back(site);
else
open_air_sites.push_back(site);
}
}
}
if (!use_buildingplan)
{
size_t last_open_air_count = 0;
while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count)
{
last_open_air_count = open_air_sites.size();
deque<building_site> current_open_air_list = open_air_sites;
open_air_sites.clear();
for (deque<building_site>::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++)
{
if (is_orthogonal_to_pending_construction(*it))
valid_building_sites.push_back(*it);
else
open_air_sites.push_back(*it);
}
}
}
return valid_building_sites.size() > 0;
}
static bool is_buildingplan_enabled()
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
Lua::StackUnwinder top(L);
if (!(lua_checkstack(L, 1) &&
Lua::PushModulePublic(out, L, "plugins.buildingplan", "isEnabled") &&
Lua::SafeCall(out, L, 0, 1)))
{
return false;
}
return lua_toboolean(L, -1);
}
static bool is_buildingplan_planmode_enabled(
df::building_type type, int16_t subtype, int32_t custom)
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 4) ||
!Lua::PushModulePublic(
out, L, "plugins.buildingplan", "isPlanModeEnabled"))
return false;
Lua::Push(L, type);
Lua::Push(L, subtype);
Lua::Push(L, custom);
if (!Lua::SafeCall(out, L, 3, 1))
return false;
return lua_toboolean(L, -1);
}
static bool is_buildingplan_managed()
{
return is_buildingplan_enabled() &&
is_buildingplan_planmode_enabled(ui_build_selector->building_type,
ui_build_selector->building_subtype,
ui_build_selector->custom_type);
}
static bool build_with_buildingplan_box_select(const df::coord &pos)
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
CoreSuspendClaimer suspend;
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 5) ||
!Lua::PushModulePublic(
out, L, "plugins.automaterial",
"build_with_buildingplan_box_select"))
{
return false;
}
Lua::Push(L, ui_build_selector->building_subtype);
Lua::Push(L, pos.x);
Lua::Push(L, pos.y);
Lua::Push(L, pos.z);
if (!Lua::SafeCall(out, L, 4, 1))
return false;
return lua_toboolean(L, -1);
}
static bool build_with_buildingplan_ui()
{
auto L = Lua::Core::State;
color_ostream_proxy out(Core::getInstance().getConsole());
CoreSuspendClaimer suspend;
Lua::StackUnwinder top(L);
return lua_checkstack(L, 1) &&
Lua::PushModulePublic(out, L, "plugins.automaterial",
"build_with_buildingplan_ui") &&
Lua::SafeCall(out, L, 0, 1);
}
static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item)
{
auto newinst = Buildings::allocInstance(pos, building_type::Construction, type);
if (!newinst)
return false;
vector<df::item*> items;
items.push_back(item);
Maps::ensureTileBlock(pos);
if (!Buildings::constructWithItems(newinst, items))
{
delete newinst;
return false;
}
return true;
}
//END Building and Verification
//START Viewscreen Hook
struct jobutils_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 select_material_at_index(size_t i)
{
ui_build_selector->sel_index = i;
std::set< df::interface_key > keys;
keys.insert(df::interface_key::SELECT_ALL);
this->feed(&keys);
return !in_material_choice_stage();
}
bool choose_materials()
{
if (!auto_choose_materials || get_curr_constr_prefs().size() == 0)
return false;
size_t size = ui_build_selector->choices.size();
for (size_t i = 0; i < size; i++)
{
MaterialDescriptor material = get_material_in_list(i);
size_t j;
if (is_material_in_autoselect(j, material))
{
return select_material_at_index(i);
}
}
return false;
}
void draw_box_selection()
{
if (!box_select_enabled)
return;
if (ui->main.mode != df::ui_sidebar_mode::Build ||
ui_build_selector->building_type != df::building_type::Construction)
return;
df::coord vport = Gui::getViewportPos();
//Even if selection drawing is disabled, paint a green cursor as we can place box selection anywhere
if (box_select_mode == SELECT_FIRST || (!show_box_selection && box_select_mode == SELECT_SECOND))
{
int32_t x, y, z;
if (!Gui::getCursorCoords(x, y, z))
return;
x = x - vport.x + 1;
y = y - vport.y + 1;
OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */);
}
else if (show_box_selection && box_select_mode == SELECT_SECOND)
{
if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z))
return;
Gui::DwarfmodeDims dims = Gui::getDwarfmodeViewDims();
int32_t startx = std::max((int32_t)vport.x, std::min(box_first.x, box_second.x));
int32_t endx = std::min(vport.x + dims.map_x2 - dims.map_x1, std::max(box_first.x, box_second.x));
int32_t starty = std::max((int32_t)vport.y, std::min(box_first.y, box_second.y));
int32_t endy = std::min(vport.y + dims.map_y2 - dims.map_y1, std::max(box_first.y, box_second.y));
for (int32_t yB = starty; yB <= endy; ++yB) {
for (int32_t xB = startx; xB <= endx; ++xB) {
if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y))
continue;
int8_t color = (xB == box_second.x && yB == box_second.y) ? COLOR_GREEN : COLOR_BROWN;
int32_t x = xB - vport.x + 1;
int32_t y = yB - vport.y + 1;
OutputString(color, x, y, "X", false, 0, 0, true /* map */);
}
}
}
else if (show_box_selection && box_select_mode == SELECT_MATERIALS)
{
for (deque<building_site>::iterator it = valid_building_sites.begin(); it != valid_building_sites.end(); it++)
{
int32_t x = it->pos.x - vport.x + 1;
int32_t y = it->pos.y - vport.y + 1;
OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */);
}
}
}
void reset_existing_selection()
{
for (int i = 0; i < 10; i++)
{
send_key(df::interface_key::BUILDING_DIM_Y_DOWN);
send_key(df::interface_key::BUILDING_DIM_X_DOWN);
}
}
void handle_input(set<df::interface_key> *input)
{
if (ui_build_selector->building_subtype >= 7)
return;
if (in_material_choice_stage())
{
if (input->count(interface_key::LEAVESCREEN))
{
box_select_mode = SELECT_FIRST;
}
MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index);
if (material.valid)
{
if (input->count(interface_key::SELECT) || input->count(interface_key::SELECT_ALL))
{
if (get_last_moved_material().matches(material))
last_used_moved = false; //Keep selected material on top
set_last_used_material(material);
if (box_select_enabled)
{
auto curr_index = ui_build_selector->sel_index;
vector<MaterialDescriptor> gen_material;
gen_material.push_back(get_material_in_list(curr_index));
box_select_materials.clear();
// Populate material list with selected material
populate_box_materials(gen_material, ((input->count(interface_key::SELECT_ALL) && ui_build_selector->is_grouped) ? -1 : 1));
input->clear(); // Let the apply_box_selection routine allocate the construction
input->insert(interface_key::LEAVESCREEN);
}
}
else if (input->count(interface_key::CUSTOM_A))
{
check_autoselect(material, true);
input->clear();
}
}
}
else if (in_placement_stage())
{
bool use_buildingplan = is_buildingplan_managed();
if (!use_buildingplan && input->count(interface_key::CUSTOM_A))
{
auto_choose_materials = !auto_choose_materials;
}
else if (!use_buildingplan && input->count(interface_key::CUSTOM_T))
{
revert_to_last_used_type = !revert_to_last_used_type;
}
else if (input->count(interface_key::CUSTOM_B))
{
reset_existing_selection();
box_select_enabled = !box_select_enabled;
if (!box_select_enabled)
cancel_box_selection();
return;
}
else if (!use_buildingplan && input->count(interface_key::CUSTOM_O))
{
allow_future_placement = !allow_future_placement;
}
else if (input->count(interface_key::LEAVESCREEN))
{
switch (box_select_mode)
{
case SELECT_FIRST:
case SELECT_SECOND:
cancel_box_selection();
default:
break;
}
}
else if (box_select_enabled)
{
if (input->count(interface_key::SELECT))
{
switch (box_select_mode)
{
case SELECT_FIRST:
if (!Gui::getCursorCoords(box_first.x, box_first.y, box_first.z))
{
cancel_box_selection();
return;
}
box_select_mode = SELECT_SECOND;
if (!show_box_selection)
Gui::setDesignationCoords(box_first.x, box_first.y, box_first.z);
input->clear();
return;
case SELECT_SECOND:
if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z))
{
cancel_box_selection();
return;
}
cancel_box_selection();
input->clear();
apply_box_selection(true);
return;
default:
break;
}
}
else if (input->count(interface_key::CUSTOM_X))
{
show_box_selection = !show_box_selection;
if (box_select_mode == SELECT_SECOND)
{
if (show_box_selection)
{
Gui::setDesignationCoords(-1, -1, -1);
}
else
{
Gui::setDesignationCoords(box_first.x, box_first.y, box_first.z);
}
}
}
else if (input->count(interface_key::CUSTOM_H))
{
hollow_selection = !hollow_selection;
}
else if (input->count(interface_key::BUILDING_DIM_Y_UP) ||
input->count(interface_key::BUILDING_DIM_Y_DOWN) ||
input->count(interface_key::BUILDING_DIM_X_UP) ||
input->count(interface_key::BUILDING_DIM_X_DOWN))
{
input->clear();
return;
}
}
else if (use_buildingplan
&& ui_build_selector->errors.size() == 0
&& input->count(interface_key::SELECT))
{
build_with_buildingplan_ui();
Gui::refreshSidebar();
input->clear();
return;
}
}
}
//END UI Methods
//START Building Application
bool populate_box_materials(vector<MaterialDescriptor> &gen_materials, int32_t count = -1)
{
bool result = false;
if (gen_materials.size() == 0)
return result;
if (ui_build_selector->is_grouped)
send_key(interface_key::BUILDING_EXPAND_CONTRACT);
size_t size = ui_build_selector->choices.size();
vector<MaterialDescriptor>::iterator gen_material;
for (size_t i = 0; i < size; i++)
{
if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i]))
{
for (gen_material = gen_materials.begin(); gen_material != gen_materials.end(); gen_material++)
{
if (gen_material->item_type == spec->candidate->getType() &&
gen_material->item_subtype == spec->candidate->getSubtype() &&
gen_material->type == spec->candidate->getActualMaterial() &&
gen_material->index == spec->candidate->getActualMaterialIndex())
{
box_select_materials.push_back(spec->candidate);
if (count > -1)
return true; // Right now we only support 1 or all materials
result = true;
break;
}
}
}
}
send_key(interface_key::BUILDING_EXPAND_CONTRACT);
return result;
}
void move_cursor(df::coord &pos)
{
int32_t x, y, z;
Gui::getCursorCoords(x, y, z);
DEBUG(log).print("moving cursor from %d, %d, %d to %d, %d, %d\n",
x, y, z, pos.x, pos.y, pos.z);
Gui::setCursorCoords(pos.x, pos.y, pos.z);
Gui::refreshSidebar();
}
void move_cursor(coord32_t &pos)
{
df::coord c((int16_t) pos.x, (int16_t) pos.y, (int16_t) pos.z);
move_cursor(c);
}
void apply_box_selection(bool new_start)
{
static bool saved_revert_setting = false;
static bool auto_select_applied = false;
bool use_buildingplan = is_buildingplan_managed();
box_select_mode = SELECT_MATERIALS;
if (new_start)
{
bool ok_to_continue = false;
bool in_future_placement_mode = false;
if (!find_valid_building_sites(false, use_buildingplan))
{
if (allow_future_placement)
{
in_future_placement_mode = find_valid_building_sites(true, use_buildingplan);
}
}
else
{
ok_to_continue = true;
}
// if using buildingplan, we don't need an anchor
if (!use_buildingplan)
{
if (in_future_placement_mode)
{
ok_to_continue =
find_anchor_in_spiral(valid_building_sites[0].pos);
}
else if (ok_to_continue)
{
// First valid site is guaranteed to be anchored, either on
// a tile or against a valid orthogonal tile
// Use it as an anchor point to generate materials list
anchor = valid_building_sites.front();
valid_building_sites.pop_front();
valid_building_sites.push_back(anchor);
}
}
if (!ok_to_continue)
{
cancel_box_selection();
hollow_selection = false;
return;
}
saved_revert_setting = revert_to_last_used_type;
revert_to_last_used_type = true;
auto_select_applied = false;
box_select_materials.clear();
}
while (valid_building_sites.size() > 0)
{
building_site site = valid_building_sites.front();
valid_building_sites.pop_front();
if (use_buildingplan)
{
// we don't actually care if this fails. buildingplan will return
// false when it filters out bad tiles, and that's ok.
build_with_buildingplan_box_select(site.pos);
continue;
}
if (box_select_materials.size() > 0)
{
df::construction_type type = (df::construction_type) ui_build_selector->building_subtype;
df::item *item = NULL;
while (box_select_materials.size() > 0)
{
item = box_select_materials.front();
if (!item->flags.bits.in_job)
break;
box_select_materials.pop_front();
item = NULL;
}
if (item != NULL)
{
if (designate_new_construction(site.pos, type, item))
{
box_select_materials.pop_front();
box_select_mode = AUTOSELECT_MATERIALS;
send_key(interface_key::LEAVESCREEN); //Must do this to register items in use
send_key(hotkeys[type]);
box_select_mode = SELECT_MATERIALS;
}
continue;
}
}
// Generate material list using regular construction placement routine
if (site.in_open_air)
{
// Cannot invoke material selection on an unconnected tile, use anchor instead
move_cursor(anchor.pos);
send_key(df::interface_key::SELECT);
}
move_cursor(site.pos);
if (!site.in_open_air)
send_key(df::interface_key::SELECT);
if (in_material_choice_stage())
{
valid_building_sites.push_front(site); //Redo current tile with whatever gets selected
if (!auto_select_applied)
{
// See if any auto select materials are available
auto_select_applied = true;
if (auto_choose_materials && populate_box_materials(preferred_materials[ui_build_selector->building_subtype]))
{
continue;
}
}
last_used_moved = false;
return; // No auto select materials left, ask user
}
}
// Allocation done, reset
move_cursor(box_second);
// if we're using buildingplan, we never actually leave the placement
// screen, so there's no need to re-enter the screen
revert_to_last_used_type = saved_revert_setting;
if (!use_buildingplan && !revert_to_last_used_type)
{
send_key(df::interface_key::LEAVESCREEN);
}
cancel_box_selection();
hollow_selection = false;
ignore_selection = 0;
}
//END Building Application
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (ignore_selection < SELECTION_IGNORE_TICKS)
{
//FIXME: Sometimes there's an extra ENTER key left over after box selection
ignore_selection = SELECTION_IGNORE_TICKS;
return;
}
if (box_select_mode != AUTOSELECT_MATERIALS)
handle_input(input);
int16_t last_used_constr_subtype = (in_material_choice_stage()) ? ui_build_selector->building_subtype : -1;
INTERPOSE_NEXT(feed)(input);
if (revert_to_last_used_type &&
last_used_constr_subtype >= 0 &&
in_type_choice_stage() &&
hotkeys.find(last_used_constr_subtype) != hotkeys.end())
{
input->clear();
input->insert(hotkeys[last_used_constr_subtype]);
INTERPOSE_NEXT(feed)(input);
if (box_select_mode == SELECT_MATERIALS)
{
apply_box_selection(false);
}
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
if (ignore_selection < SELECTION_IGNORE_TICKS)
{
++ignore_selection;
}
if (in_material_choice_stage())
{
if (!last_used_moved && ui_build_selector->is_grouped)
{
last_used_moved = true;
if (!box_select_enabled && choose_materials())
{
return;
}
else
{
move_material_to_top(get_last_used_material());
}
}
else if (!ui_build_selector->is_grouped)
{
last_used_moved = false;
}
}
else
{
last_used_moved = false;
}
INTERPOSE_NEXT(render)();
draw_box_selection();
if (in_type_choice_stage())
{
cancel_box_selection();
return;
}
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = 25;
if (in_material_choice_stage())
{
MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index);
if (material.valid)
{
AMOutputToggleString(x, y, "Autoselect", "a", check_autoselect(material, false), true, left_margin);
if (box_select_mode == SELECT_MATERIALS)
{
++y;
OutputString(COLOR_BROWN, x, y, "Construction:", true, left_margin);
OutputString(COLOR_WHITE, x, y, int_to_string(valid_building_sites.size()) + " tiles to fill", true, left_margin);
}
}
}
else if (in_placement_stage() && ui_build_selector->building_subtype < 7)
{
bool use_buildingplan = is_buildingplan_managed();
OutputString(COLOR_BROWN, x, y, "DFHack Automaterial Options", true, left_margin);
if (use_buildingplan)
{
y += 2;
}
else
{
AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin);
AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin);
}
++y;
AMOutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin);
if (box_select_enabled)
{
AMOutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin);
OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin);
if (use_buildingplan)
++y;
else
AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin);
}
else
{
y += 3;
}
y += 2;
if (is_buildingplan_enabled())
OutputString(COLOR_BROWN, x, y, "DFHack Buildingplan Options", true, left_margin);
if (box_select_enabled)
{
Screen::Pen pen(' ',COLOR_BLACK);
y = dims.y1 + 2;
Screen::fillRect(pen, x, y, dims.menu_x2, y + 17);
y += 2;
switch (box_select_mode)
{
case SELECT_FIRST:
OutputString(COLOR_BROWN, x, y, "Choose first corner", true, left_margin);
break;
case SELECT_SECOND:
{
OutputString(COLOR_GREEN, x, y, "Choose second corner", true, left_margin);
int32_t curr_x, curr_y, curr_z;
Gui::getCursorCoords(curr_x, curr_y, curr_z);
int dX = abs(box_first.x - curr_x) + 1;
int dY = abs(box_first.y - curr_y) + 1;
stringstream label;
label << "Selection: " << dX << "x" << dY;
OutputString(COLOR_WHITE, x, ++y, label.str(), true, left_margin);
df::coord vport = Gui::getViewportPos();
int cx = box_first.x - vport.x + 1;
int cy = box_first.y - vport.y + 1;
Gui::DwarfmodeDims dims = Gui::getDwarfmodeViewDims();
if (cx >= 1 && cx <= dims.map_x2 && cy >= 1 && cy <= dims.map_y2)
OutputString(COLOR_BROWN, cx, cy, "X", false, 0, 0, true /* map */);
break;
}
default:
break;
}
OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin);
}
}
}
};
//END Viewscreen Hook
color_ostream_proxy console_out(Core::getInstance().getConsole());
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render);
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{
if (!gps)
return CR_FAILURE;
if (enable != is_enabled)
{
if (!INTERPOSE_HOOK(jobutils_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(jobutils_hook, render).apply(enable))
return CR_FAILURE;
is_enabled = enable;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
hotkeys[construction_type::Wall] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL;
hotkeys[construction_type::Floor] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FLOOR;
hotkeys[construction_type::Ramp] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_RAMP;
hotkeys[construction_type::UpStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UP;
hotkeys[construction_type::DownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_DOWN;
hotkeys[construction_type::UpDownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UPDOWN;
hotkeys[construction_type::Fortification] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FORTIFICATION;
//Ignore tracks, DF already returns to track menu
return CR_OK;
}