#include #include "df/coord2d.h" #include "df/entity_raw.h" #include "df/inorganic_raw.h" #include "df/dfhack_material_category.h" #include "df/interface_key.h" #include "df/viewscreen.h" #include "df/viewscreen_choose_start_sitest.h" #include "df/world.h" #include "df/world_raws.h" #include "finder_ui.h" #include "help_ui.h" #include "overlay.h" #include "screen.h" using df::global::world; namespace embark_assist { namespace overlay { DFHack::Plugin *plugin_self; const Screen::Pen empty_pen = Screen::Pen('\0', COLOR_YELLOW, COLOR_BLACK, false); const Screen::Pen yellow_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_YELLOW, false); const Screen::Pen green_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_GREEN, false); struct display_strings { Screen::Pen pen; std::string text; }; typedef Screen::Pen *pen_column; struct states { int blink_count = 0; bool show = true; bool matching = false; bool match_active = false; embark_update_callbacks embark_update; match_callbacks match_callback; clear_match_callbacks clear_match_callback; embark_assist::defs::find_callbacks find_callback; shutdown_callbacks shutdown_callback; Screen::Pen site_grid[16][16]; uint8_t current_site_grid = 0; std::vector embark_info; Screen::Pen local_match_grid[16][16]; pen_column *world_match_grid = nullptr; uint16_t match_count = 0; uint16_t max_inorganic; bool fileresult = false; uint8_t fileresult_pass = 0; }; static states *state = nullptr; //==================================================================== // Logic for sizing the World map to the right. df::coord2d world_dimension_size(uint16_t map_size, uint16_t region_size) { uint16_t factor = (map_size - 1 + region_size - 1) / region_size; uint16_t result = (map_size + ((factor - 1) / 2)) / factor; if (result > region_size) { result = region_size; } return{ result, factor}; } //==================================================================== class ViewscreenOverlay : public df::viewscreen_choose_start_sitest { public: typedef df::viewscreen_choose_start_sitest interpose_base; void send_key(const df::interface_key &key) { std::set< df::interface_key > keys; keys.insert(key); this->feed(&keys); } DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { // color_ostream_proxy out(Core::getInstance().getConsole()); if (input->count(df::interface_key::CUSTOM_Q)) { state->shutdown_callback(); return; } else if (input->count(df::interface_key::SETUP_LOCAL_X_MUP) || input->count(df::interface_key::SETUP_LOCAL_X_MDOWN) || input->count(df::interface_key::SETUP_LOCAL_Y_MUP) || input->count(df::interface_key::SETUP_LOCAL_Y_MDOWN) || input->count(df::interface_key::SETUP_LOCAL_X_UP) || input->count(df::interface_key::SETUP_LOCAL_X_DOWN) || input->count(df::interface_key::SETUP_LOCAL_Y_UP) || input->count(df::interface_key::SETUP_LOCAL_Y_DOWN)) { INTERPOSE_NEXT(feed)(input); state->embark_update(); } else if (input->count(df::interface_key::CUSTOM_C)) { if (state->matching) { state->matching = false; } else { state->match_active = false; state->clear_match_callback(); } } else if (input->count(df::interface_key::CUSTOM_F)) { if (!state->match_active && !state->matching) { embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic, false); } } else if (input->count(df::interface_key::CUSTOM_I)) { embark_assist::help_ui::init(embark_assist::overlay::plugin_self); } else { INTERPOSE_NEXT(feed)(input); } } //==================================================================== DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); // color_ostream_proxy out(Core::getInstance().getConsole()); auto current_screen = Gui::getViewscreenByType(0); int16_t x = current_screen->location.region_pos.x; int16_t y = current_screen->location.region_pos.y; auto width = Screen::getWindowSize().x; auto height = Screen::getWindowSize().y; state->blink_count++; if (state->blink_count == 35) { state->blink_count = 0; state->show = !state->show; } if (state->matching) state->show = true; Screen::drawBorder(" Embark Assistant "); Screen::Pen pen_lr(' ', COLOR_LIGHTRED); Screen::Pen pen_w(' ', COLOR_WHITE); Screen::Pen pen_g(' ', COLOR_GREY); Screen::paintString(pen_lr, width - 28, 20, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str(), false); Screen::paintString(pen_w, width - 27, 20, ": Embark Assistant Info", false); Screen::paintString(pen_lr, width - 28, 21, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_F).c_str(), false); Screen::paintString(pen_w, width - 27, 21, ": Find Embark ", false); Screen::paintString(pen_lr, width - 28, 22, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_C).c_str(), false); Screen::paintString(pen_w, width - 27, 22, ": Cancel/Clear Find", false); Screen::paintString(pen_lr, width - 28, 23, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_Q).c_str(), false); Screen::paintString(pen_w, width - 27, 23, ": Quit Embark Assistant", false); Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles:", false); Screen::paintString(empty_pen, width - 6, 25, to_string(state->match_count), false); Screen::paintString(pen_g, width - 28, 26, "(Those on the Region Map)", false); if (height > 25) { // Mask the vanilla DF find help as it's overridden. Screen::paintString(pen_w, 50, height - 2, " ", false); } for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { if (state->site_grid[i][k].ch) { Screen::paintTile(state->site_grid[i][k], i + 1, k + 2); } } } for (size_t i = 0; i < state->embark_info.size(); i++) { embark_assist::screen::paintString(state->embark_info[i].pen, 1, i + 19, state->embark_info[i].text, false); } if (state->show) { int16_t left_x = x - (width / 2 - 7 - 18 + 1) / 2; int16_t right_x; int16_t top_y = y - (height - 8 - 2 + 1) / 2; int16_t bottom_y; if (left_x < 0) { left_x = 0; } if (top_y < 0) { top_y = 0; } right_x = left_x + width / 2 - 7 - 18; bottom_y = top_y + height - 8 - 2; if (right_x >= world->worldgen.worldgen_parms.dim_x) { right_x = world->worldgen.worldgen_parms.dim_x - 1; left_x = right_x - (width / 2 - 7 - 18); } if (bottom_y >= world->worldgen.worldgen_parms.dim_y) { bottom_y = world->worldgen.worldgen_parms.dim_y - 1; top_y = bottom_y - (height - 8 - 2); } if (left_x < 0) { left_x = 0; } if (top_y < 0) { top_y = 0; } for (uint16_t i = left_x; i <= right_x; i++) { for (uint16_t k = top_y; k <= bottom_y; k++) { if (state->world_match_grid[i][k].ch) { Screen::paintTile(state->world_match_grid[i][k], i - left_x + 18, k - top_y + 2); } } } for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { if (state->local_match_grid[i][k].ch) { Screen::paintTile(state->local_match_grid[i][k], i + 1, k + 2); } } } df::coord2d size_factor_x = world_dimension_size(world->worldgen.worldgen_parms.dim_x, width / 2 - 24); df::coord2d size_factor_y = world_dimension_size(world->worldgen.worldgen_parms.dim_y, height - 9); for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { if (state->world_match_grid[i][k].ch) { Screen::paintTile(state->world_match_grid[i][k], width / 2 - 5 + min(size_factor_x.x - 1, i / size_factor_x.y), 2 + min(size_factor_y.x - 1, k / size_factor_y.y)); } } } } if (state->matching) { embark_assist::overlay::state->match_callback(); } } }; IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, feed); IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, render); } } //==================================================================== bool embark_assist::overlay::setup(DFHack::Plugin *plugin_self, embark_update_callbacks embark_update_callback, match_callbacks match_callback, clear_match_callbacks clear_match_callback, embark_assist::defs::find_callbacks find_callback, shutdown_callbacks shutdown_callback, uint16_t max_inorganic) { // color_ostream_proxy out(Core::getInstance().getConsole()); state = new(states); embark_assist::overlay::plugin_self = plugin_self; embark_assist::overlay::state->embark_update = embark_update_callback; embark_assist::overlay::state->match_callback = match_callback; embark_assist::overlay::state->clear_match_callback = clear_match_callback; embark_assist::overlay::state->find_callback = find_callback; embark_assist::overlay::state->shutdown_callback = shutdown_callback; embark_assist::overlay::state->max_inorganic = max_inorganic; embark_assist::overlay::state->match_active = false; state->world_match_grid = new pen_column[world->worldgen.worldgen_parms.dim_x]; if (!state->world_match_grid) { return false; // Out of memory } for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { state->world_match_grid[i] = new Screen::Pen[world->worldgen.worldgen_parms.dim_y]; if (!state->world_match_grid[i]) { // Out of memory. return false; } } clear_match_results(); return INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, feed).apply(true) && INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, render).apply(true); } //==================================================================== void embark_assist::overlay::set_sites(embark_assist::defs::site_lists *site_list) { for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { state->site_grid[i][k] = empty_pen; } } for (uint16_t i = 0; i < site_list->size(); i++) { state->site_grid[site_list->at(i).x][site_list->at(i).y].ch = site_list->at(i).type; } } //==================================================================== void embark_assist::overlay::initiate_match() { embark_assist::overlay::state->matching = true; } //==================================================================== void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done) { // color_ostream_proxy out(Core::getInstance().getConsole()); state->matching = !done; state->match_count = count; for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { if (match_results->at(i).at(k).preliminary_match) { state->world_match_grid[i][k] = yellow_x_pen; } else if (match_results->at(i).at(k).contains_match) { state->world_match_grid[i][k] = green_x_pen; } else { state->world_match_grid[i][k] = empty_pen; } } } if (done && state->fileresult) { state->fileresult_pass++; if (state->fileresult_pass == 1) { embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic, true); } else { FILE* outfile = fopen(fileresult_file_name, "w"); fprintf(outfile, "%i\n", count); fclose(outfile); } } } //==================================================================== void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) { state->embark_info.clear(); if (!site_info->incursions_processed) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTRED), "Incomp. Survey" }); } if (site_info->sand) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), "Sand" }); } if (site_info->clay) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" }); } if (site_info->coal) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), "Coal" }); } state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) }); if (site_info->flat) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Flat" }); } if (site_info->aquifer != embark_assist::defs::None_Aquifer_Bit) { std::string none = " "; std::string light = " "; std::string heavy = " "; std::string no = "No "; std::string lt = "Lt "; std::string hv = "Hv"; switch (site_info->aquifer) { case embark_assist::defs::Clear_Aquifer_Bits: case embark_assist::defs::None_Aquifer_Bit: // Neither of these should appear break; case embark_assist::defs::Light_Aquifer_Bit: light = lt; break; case embark_assist::defs::None_Aquifer_Bit | embark_assist::defs::Light_Aquifer_Bit: none = no; light = lt; break; case embark_assist::defs::Heavy_Aquifer_Bit: heavy = hv; break; case embark_assist::defs::None_Aquifer_Bit | embark_assist::defs::Heavy_Aquifer_Bit: none = no; heavy = hv; break; case embark_assist::defs::Light_Aquifer_Bit | embark_assist::defs::Heavy_Aquifer_Bit: light = lt; heavy = hv; break; case embark_assist::defs::None_Aquifer_Bit | embark_assist::defs::Light_Aquifer_Bit | embark_assist::defs::Heavy_Aquifer_Bit: none = no; light = lt; heavy = hv; break; } state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aq: " + none + light + heavy }); } if (site_info->max_waterfall > 0) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Waterfall " + std::to_string(site_info->max_waterfall) }); } if (site_info->blood_rain || site_info->permanent_syndrome_rain || site_info->temporary_syndrome_rain || site_info->reanimating || site_info->thralling) { std::string blood_rain; std::string permanent_syndrome_rain; std::string temporary_syndrome_rain; std::string reanimating; std::string thralling; if (site_info->blood_rain) { blood_rain = "BR "; } else { blood_rain = " "; } if (site_info->permanent_syndrome_rain) { permanent_syndrome_rain = "PS "; } else { permanent_syndrome_rain = " "; } if (site_info->temporary_syndrome_rain) { temporary_syndrome_rain = "TS "; } else { permanent_syndrome_rain = " "; } if (site_info->reanimating) { reanimating = "Re "; } else { reanimating = " "; } if (site_info->thralling) { thralling = "Th"; } else { thralling = " "; } state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTRED), blood_rain + temporary_syndrome_rain + permanent_syndrome_rain + reanimating + thralling }); } if (site_info->flux) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), "Flux" }); } for (auto const& i : site_info->metals) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), world->raws.inorganics[i]->id }); } for (auto const& i : site_info->economics) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), world->raws.inorganics[i]->id }); } for (uint16_t i = 0; i < site_info->neighbors.size(); i++) { if (world->raws.entities[site_info->neighbors[i]]->translation == "") { state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), world->raws.entities[site_info->neighbors[i]]->code }); // Kobolds have an empty translation field } else { state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), world->raws.entities[site_info->neighbors[i]]->translation }); } } if (site_info->necro_neighbors > 0) { state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTRED), "Towers: " + std::to_string(site_info->necro_neighbors) }); } } //==================================================================== void embark_assist::overlay::set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches) { for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { if (mlt_matches[i][k]) { state->local_match_grid[i][k] = green_x_pen; } else { state->local_match_grid[i][k] = empty_pen; } } } } //==================================================================== void embark_assist::overlay::clear_match_results() { for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { state->world_match_grid[i][k] = empty_pen; } } for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { state->local_match_grid[i][k] = empty_pen; } } } //==================================================================== void embark_assist::overlay::fileresult() { // Have to search twice, as the first pass cannot be complete due to mutual dependencies. state->fileresult = true; embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic, true); } //==================================================================== void embark_assist::overlay::shutdown() { if (state && state->world_match_grid) { INTERPOSE_HOOK(ViewscreenOverlay, render).remove(); INTERPOSE_HOOK(ViewscreenOverlay, feed).remove(); for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { delete[] state->world_match_grid[i]; } delete[] state->world_match_grid; } if (state) { state->embark_info.clear(); delete state; state = nullptr; } }