From 175edf501aa2fe77d0a3893737b063343b8e5349 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 5 Jun 2015 21:49:22 -0400 Subject: [PATCH] Add "confirm" plugin - implements a few confirmation dialogs See #577 --- plugins/CMakeLists.txt | 1 + plugins/confirm.cpp | 255 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 plugins/confirm.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f5a82e916..628c521de 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -105,6 +105,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(cleanowned cleanowned.cpp) DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(command-prompt command-prompt.cpp) + DFHACK_PLUGIN(confirm confirm.cpp) DFHACK_PLUGIN(createitem createitem.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(deramp deramp.cpp) diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp new file mode 100644 index 000000000..3c1497c87 --- /dev/null +++ b/plugins/confirm.cpp @@ -0,0 +1,255 @@ +#include +#include +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" +#include "VTableInterpose.h" +#include "uicommon.h" +#include "df/viewscreen_tradegoodsst.h" + +using namespace DFHack; + +DFHACK_PLUGIN("confirm"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(gps); + +typedef std::set ikey_set; +command_result df_confirm (color_ostream &out, std::vector & parameters); + +static std::multimap hooks; + +#define IMPLEMENT_CONFIRMATION_HOOKS(cls) \ +static cls cls##_instance; \ +struct cls##_hooks : cls::screen_type { \ + typedef cls::screen_type interpose_base; \ + DEFINE_VMETHOD_INTERPOSE(void, feed, (ikey_set *input)) \ + { \ + cls##_instance.screen = this; \ + if (!cls##_instance.feed(input)) \ + INTERPOSE_NEXT(feed)(input); \ + } \ + DEFINE_VMETHOD_INTERPOSE(void, render, ()) \ + { \ + cls##_instance.screen = this; \ + INTERPOSE_NEXT(render)(); \ + cls##_instance.render(); \ + } \ + DEFINE_VMETHOD_INTERPOSE(bool, key_conflict, (df::interface_key key)) \ + { \ + return cls##_instance.key_conflict(key) || INTERPOSE_NEXT(key_conflict)(key); \ + } \ +}; \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, feed); \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, render); \ +IMPLEMENT_VMETHOD_INTERPOSE(cls##_hooks, key_conflict); + +template +class confirmation { +public: + enum cstate { INACTIVE, ACTIVE, SELECTED }; + typedef T screen_type; + screen_type *screen; + bool feed (ikey_set *input) { + if (state == INACTIVE) + { + for (auto it = input->begin(); it != input->end(); ++it) + { + if (intercept_key(*it)) + { + last_key = *it; + state = ACTIVE; + return true; + } + } + return false; + } + else if (state == ACTIVE) + { + if (input->count(df::interface_key::LEAVESCREEN)) + state = INACTIVE; + else if (input->count(df::interface_key::SELECT)) + state = SELECTED; + return true; + } + return false; + } + bool key_conflict (df::interface_key key) + { + if (key == df::interface_key::SELECT || key == df::interface_key::LEAVESCREEN) + return false; + return state == ACTIVE; + } + void render() { + static std::vector lines; + Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK); + Screen::Pen corner_dr = Screen::Pen((char)188, COLOR_GREY, COLOR_BLACK); + Screen::Pen border_ud = Screen::Pen((char)205, COLOR_GREY, COLOR_BLACK); + Screen::Pen border_lr = Screen::Pen((char)186, COLOR_GREY, COLOR_BLACK); + if (state == ACTIVE) + { + split_string(&lines, get_message(), "\n"); + size_t max_length = 30; + for (auto it = lines.begin(); it != lines.end(); ++it) + max_length = std::max(max_length, it->size()); + int width = max_length + 4; + int height = lines.size() + 4; + int x1 = (gps->dimx / 2) - (width / 2); + int x2 = x1 + width - 1; + int y1 = (gps->dimy / 2) - (height / 2); + int y2 = y1 + height - 1; + for (int x = x1; x <= x2; x++) + { + Screen::paintTile(border_ud, x, y1); + Screen::paintTile(border_ud, x, y2); + } + for (int y = y1; y <= y2; y++) + { + Screen::paintTile(border_lr, x1, y); + Screen::paintTile(border_lr, x2, y); + } + Screen::paintTile(corner_ul, x1, y1); + Screen::paintTile(corner_ur, x2, y1); + Screen::paintTile(corner_dl, x1, y2); + Screen::paintTile(corner_dr, x2, y2); + std::string title = " " + get_title() + " "; + Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), + (gps->dimx / 2) - (title.size() / 2), y1, title); + int x = x1 + 2; + OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN)); + OutputString(COLOR_WHITE, x, y2, ": Cancel"); + x = x2 - 2 - 4 - Screen::getKeyDisplay(df::interface_key::SELECT).size(); + OutputString(COLOR_LIGHTGREEN, x, y2, Screen::getKeyDisplay(df::interface_key::SELECT)); + OutputString(COLOR_WHITE, x, y2, ": Ok"); + Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), x1 + 1, y1 + 1, x2 - 1, y2 - 1); + for (size_t i = 0; i < lines.size(); i++) + { + Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]); + } + } + else if (state == SELECTED) + { + ikey_set tmp; + tmp.insert(last_key); + screen->feed(&tmp); + state = INACTIVE; + } + } + virtual bool intercept_key (df::interface_key key) = 0; + virtual std::string get_title() { return "Confirm"; } + virtual std::string get_message() = 0; + virtual UIColor get_color() { return COLOR_YELLOW; } +protected: + cstate state; + df::interface_key last_key; +}; + +class trade_seize_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_SEIZE; } + virtual std::string get_id() { return "trade-seize"; } + virtual std::string get_title() { return "Confirm seize"; } + virtual std::string get_message() { return "Are you sure you want to sieze these goods?"; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_seize_confirmation); + +class trade_offer_confirmation : public confirmation { +public: + virtual bool intercept_key (df::interface_key key) { return key == df::interface_key::TRADE_OFFER; } + virtual std::string get_id() { return "trade-offer"; } + virtual std::string get_title() { return "Confirm offer"; } + virtual std::string get_message() { return "Are you sure you want to offer these goods?\nYou will receive no payment."; } +}; +IMPLEMENT_CONFIRMATION_HOOKS(trade_offer_confirmation); + +#define CHOOK(cls) \ + HOOK_ACTION(cls, render) \ + HOOK_ACTION(cls, feed) \ + HOOK_ACTION(cls, key_conflict) + +#define CHOOKS \ + CHOOK(trade_seize_confirmation) \ + CHOOK(trade_offer_confirmation) + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + std::vector hook_names; +#define HOOK_ACTION(cls, method) hooks.insert(std::pair(cls##_instance.get_id(), INTERPOSE_HOOK(cls##_hooks, method))); \ + if (std::find(hook_names.begin(), hook_names.end(), cls##_instance.get_id()) == hook_names.end()) hook_names.push_back(cls##_instance.get_id()); + CHOOKS +#undef HOOK_ACTION + std::string help = + " confirmation enable|disable option|all ...\n" + "Available options:\n " + join_strings(", ", hook_names); + commands.push_back(PluginCommand( + "confirm", + "Confirmation dialogs", + df_confirm, + false, //allow non-interactive use + help.c_str() + )); + return CR_OK; +} + +DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) +{ + if (is_enabled != enable) + { +#define HOOK_ACTION(cls, method) !INTERPOSE_HOOK(cls##_hooks, method).apply(enable) || + if (CHOOKS 0) + return CR_FAILURE; +#undef HOOK_ACTION + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ +#define HOOK_ACTION(cls, method) INTERPOSE_HOOK(cls##_hooks, method).remove(); + CHOOKS; +#undef HOOK_ACTION + return CR_OK; +} + +void enable_conf (color_ostream &out, std::string name, bool state) +{ + bool found = false; + for (auto it = hooks.begin(); it != hooks.end(); ++it) + { + if (it->first == name) + { + found = true; + it->second.apply(state); + } + } + if (!found) + out.printerr("Unrecognized option: %s\n", name.c_str()); +} + +command_result df_confirm (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + bool state = true; + for (auto it = parameters.begin(); it != parameters.end(); ++it) + { + if (*it == "enable") + state = true; + else if (*it == "disable") + state = false; + else if (*it == "all") + { + for (auto it = hooks.begin(); it != hooks.end(); ++it) + { + it->second.apply(state); + } + } + else + enable_conf(out, *it, state); + } + return CR_OK; +}