dfhack/plugins/embark-assistant/overlay.cpp

560 lines
21 KiB
C++

#include <modules/Gui.h>
#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<display_strings> 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<df::interface_key> *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<df::viewscreen_choose_start_sitest>(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::aquifer_sizes::None) {
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::aquifer_sizes::NA:
case embark_assist::defs::aquifer_sizes::None: // Neither of these should appear
break;
case embark_assist::defs::aquifer_sizes::Light:
light = lt;
break;
case embark_assist::defs::aquifer_sizes::None_Light:
none = no;
light = lt;
break;
case embark_assist::defs::aquifer_sizes::Heavy:
heavy = hv;
break;
case embark_assist::defs::aquifer_sizes::None_Heavy:
none = no;
heavy = hv;
break;
case embark_assist::defs::aquifer_sizes::Light_Heavy:
light = lt;
heavy = hv;
break;
case embark_assist::defs::aquifer_sizes::None_Light_Heavy:
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;
}
}