dfhack/plugins/embark-assistant/overlay.cpp

440 lines
18 KiB
C++

#include <modules/Gui.h>
#include "df/coord2d.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 region_match_grid[16][16];
pen_column *world_match_grid = nullptr;
uint16_t match_count = 0;
uint16_t max_inorganic;
};
static states *state = nullptr;
//====================================================================
/* // Attempt to replicate the DF logic for sizing the right world map. This
// code seems to compute the values correctly, but the author hasn't been
// able to apply them at the same time as DF does to 100%.
// DF seems to round down on 0.5 values.
df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) {
uint16_t result;
for (uint16_t factor = 1; factor < 17; factor++) {
result = map_size / factor;
if ((map_size - result * factor) * 2 != factor) {
result = (map_size + factor / 2) / factor;
}
if (result <= available_screen) {
return {result, factor};
}
}
return{16, 16}; // Should never get here.
}
*/
//====================================================================
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)) {
state->match_active = false;
state->matching = 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);
}
}
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::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);
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->region_match_grid[i][k].ch) {
Screen::paintTile(state->region_match_grid[i][k], i + 1, k + 2);
}
}
}
/* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there.
Screen::Pen pen(' ', COLOR_YELLOW);
// Boundaries of the top level world map
Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant
// Screen::paintString(pen, width - 30, 2, "X", false); // Marks UR corner of right world map area.
// Screen::paintString(pen, width / 2 - 5, height - 8, "X", false); // BL corner of right world map area.
// Screen::paintString(pen, width - 30, height - 8, "X", false); // BR corner of right world map area.
uint16_t l_width = width - 30 - (width / 2 - 5) + 1; // Horizontal space available for right world map.
uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for right world map.
df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x);
df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y);
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2, "X", false);
Screen::paintString(pen, width / 2 - 5, 2 + size_factor_y.x - 1, "X", false);
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2 + size_factor_y.x - 1, "X", false);
*/
}
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;
}
}
}
}
//====================================================================
void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) {
state->embark_info.clear();
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" });
}
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) {
if (site_info->aquifer_full) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Aquifer" });
}
else {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aquifer" });
}
}
if (site_info->waterfall) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Waterfall" });
}
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 });
}
}
//====================================================================
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->region_match_grid[i][k] = green_x_pen;
}
else {
state->region_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->region_match_grid[i][k] = empty_pen;
}
}
}
//====================================================================
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;
}
}