diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 76664b91f..ea7fd7457 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -21,9 +21,20 @@ #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 "modules/Gui.h" #include "modules/Screen.h" +#include "modules/Items.h" +#include "modules/Constructions.h" +#include "modules/Buildings.h" +#include "modules/Maps.h" + +#include "TileTypes.h" +#include "df/job_item.h" using std::map; using std::string; @@ -55,14 +66,6 @@ struct MaterialDescriptor } }; -static map last_used_material; -static map last_moved_material; -static map< int16_t, vector > preferred_materials; -static map< int16_t, df::interface_key > hotkeys; -static bool last_used_moved = false; -static bool auto_choose_materials = true; -static bool auto_choose_attempted = true; -static bool revert_to_last_used_type = false; static command_result automaterial_cmd(color_ostream &out, vector & parameters) { @@ -74,7 +77,6 @@ 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); @@ -95,6 +97,36 @@ void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bo OutputString(color, x, y, display, newline, left_margin); } +static string int_to_string(int i) +{ + return static_cast( &(ostringstream() << i))->str(); +} + +//START UI Functions +struct coord32_t +{ + int32_t x, y, z; +}; + +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 box_select_materials; + +#define SELECTION_IGNORE_TICKS 10 +static int ignore_selection = SELECTION_IGNORE_TICKS; + +static map last_used_material; +static map last_moved_material; +static map< int16_t, vector > preferred_materials; +static map< int16_t, df::interface_key > hotkeys; +static bool last_used_moved = false; +static bool auto_choose_materials = true; +static bool auto_choose_attempted = true; +static bool revert_to_last_used_type = false; +static bool allow_future_placement = false; static inline bool in_material_choice_stage() { @@ -170,8 +202,8 @@ static MaterialDescriptor get_material_in_list(size_t i) } else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) { - result.item_type = gen->item_type; - result.item_subtype = gen->item_subtype; + 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; @@ -240,12 +272,365 @@ static bool check_autoselect(MaterialDescriptor &material, bool toggle) } } +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 valid_building_sites; +static deque open_air_sites; +static building_site anchor; + +static bool is_orthogonal_to_pending_construction(building_site &site) +{ + for (deque::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(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 == 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) + return false; + } + else + { + auto material = tileMaterial(*ttype); + if (shape == tiletype_shape::RAMP) + { + if (material == tiletype_material::CONSTRUCTION) + return false; + } + else + { + if (shape_basic != tiletype_shape_basic::Floor) + return false; + + if (material == tiletype_material::CONSTRUCTION) + { + // Can build on top of a wall, but not on a constructed floor + df::coord pos_below = site.pos; + pos_below.z--; + if (!Maps::isValidTilePos(pos_below)) + return false; + + auto ttype = Maps::getTileType(pos_below); + if (!ttype) + return false; + + auto shape = tileShape(*ttype); + auto shapeBasic = tileShapeBasic(shape); + if (tileShapeBasic(shape) != tiletype_shape_basic::Wall) + return false; + } + + if (shape == tiletype_shape::TREE) + 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) +{ + 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 (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); + } + } + } + + 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 current_open_air_list = open_air_sites; + open_air_sites.clear(); + for (deque::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 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 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++) { @@ -253,31 +638,109 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest size_t j; if (is_material_in_autoselect(j, material)) { - ui_build_selector->sel_index = i; - std::set< df::interface_key > keys; - keys.insert(df::interface_key::SELECT_ALL); - this->feed(&keys); - if (!in_material_choice_stage()) - return true; + return select_material_at_index(i); } } return false; } - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + void draw_box_selection() { + if (!box_select_enabled) + 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"); + } + else if (show_box_selection && box_select_mode == SELECT_SECOND) + { + if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z)) + return; + + int32_t xD = (box_second.x > box_first.x) ? 1 : -1; + int32_t 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; + + 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"); + } + } + } + else if (show_box_selection && box_select_mode == SELECT_MATERIALS) + { + for (deque::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"); + } + } + } + + 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 *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::SEC_SELECT)) { if (get_last_moved_material().matches(material)) - last_used_moved = false; + 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 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::SEC_SELECT) && 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)) { @@ -296,39 +759,324 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { 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 (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; + } + } + } + } + //END UI Methods + + //START Building Application + bool populate_box_materials(vector &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::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) + { + Gui::setCursorCoords(pos.x, pos.y, pos.z); + send_key(interface_key::CURSOR_DOWN_Z); + send_key(interface_key::CURSOR_UP_Z); + } + + 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; + + 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)) + { + if (allow_future_placement) + { + in_future_placement_mode = find_valid_building_sites(true); + } + } + else + { + ok_to_continue = true; + } + + 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 (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); + + revert_to_last_used_type = saved_revert_setting; + if (!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 *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_material_choice_stage() && + in_type_choice_stage() && hotkeys.find(last_used_constr_subtype) != hotkeys.end()) { input->clear(); input->insert(hotkeys[last_used_constr_subtype]); - this->feed(input); + 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) + if (!last_used_moved && ui_build_selector->is_grouped) { - if (auto_choose_materials && get_curr_constr_prefs().size() > 0) + last_used_moved = true; + if (!box_select_enabled && choose_materials()) { - last_used_moved = true; - if (choose_materials()) - { - return; - } + return; } - else if (ui_build_selector->is_grouped) + else { - last_used_moved = true; move_material_to_top(get_last_used_material()); } } @@ -344,15 +1092,23 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest 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) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; - string toggle_string = "Enable"; string title = "Disabled"; if (check_autoselect(material, false)) @@ -363,23 +1119,59 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest OutputString(COLOR_BROWN, x, y, "DFHack Autoselect: " + title, true, left_margin); OutputHotkeyString(x, y, toggle_string.c_str(), "a", 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() + 1) + " tiles to fill", true, left_margin); + } } } - else if (in_placement_stage() && ui_build_selector->building_subtype != 7) + else if (in_placement_stage() && ui_build_selector->building_subtype < 7) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; - string autoselect_toggle_string = (auto_choose_materials) ? "Disable Auto Mat-select" : "Enable Auto Mat-select"; string revert_toggle_string = (revert_to_last_used_type) ? "Disable Auto Type-select" : "Enable Auto Type-select"; OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); OutputHotkeyString(x, y, autoselect_toggle_string.c_str(), "a", true, left_margin); OutputHotkeyString(x, y, revert_toggle_string.c_str(), "t", true, left_margin); + + ++y; + OutputHotkeyString(x, y, (box_select_enabled) ? "Disable Box Select" : "Enable Box Select", "b", true, left_margin); + if (box_select_enabled) + { + OutputHotkeyString(x, y, (show_box_selection) ? "Disable Box Marking" : "Enable Box Marking", "x", true, left_margin); + OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); + OutputHotkeyString(x, y, (allow_future_placement) ? "Disable Open Placement" : "Enable Open Placement", "o", true, left_margin); + } + ++y; + 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); + int cx = box_first.x; + int cy = box_first.y; + OutputString(COLOR_BROWN, cx, cy, "X"); + } + + OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); + } } } }; +//END Viewscreen Hook color_ostream_proxy console_out(Core::getInstance().getConsole());