remove confirm plugin (replaced by script)

develop
Myk Taylor 2024-01-01 17:46:31 -08:00
parent fa0732d651
commit ac91e2096f
No known key found for this signature in database
5 changed files with 0 additions and 1038 deletions

@ -7,7 +7,6 @@
# Enable system services
enable buildingplan
enable burrow
enable confirm
enable faststart
enable logistics
enable overlay

@ -1,21 +0,0 @@
confirm
=======
.. dfhack-tool::
:summary: Adds confirmation dialogs for destructive actions.
:tags: fort interface
In the base game, it is frightenly easy to destroy hours of work with a single
misclick. Now you can avoid the consequences of accidentally disbanding a squad
(for example), or deleting a hauling route.
Usage
-----
``enable confirm``, ``confirm enable all``
Enable all confirmation options. Replace with ``disable`` to disable all.
``confirm enable option1 [option2...]``
Enable (or ``disable``) specific confirmation dialogs.
When run without parameters, ``confirm`` will report which confirmation dialogs
are currently enabled.

@ -100,7 +100,6 @@ if(BUILD_SUPPORTED)
dfhack_plugin(cleanconst cleanconst.cpp)
dfhack_plugin(cleaners cleaners.cpp)
dfhack_plugin(cleanowned cleanowned.cpp)
dfhack_plugin(confirm confirm.cpp LINK_LIBRARIES lua)
dfhack_plugin(createitem createitem.cpp)
dfhack_plugin(cursecheck cursecheck.cpp)
dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua)

@ -1,728 +0,0 @@
#include <map>
#include <set>
#include <queue>
#include "Console.h"
#include "Core.h"
#include "DataDefs.h"
#include "Debug.h"
#include "Error.h"
#include "Export.h"
#include "LuaTools.h"
#include "LuaWrapper.h"
#include "PluginManager.h"
#include "VTableInterpose.h"
#include "modules/Gui.h"
#include "uicommon.h"
#include "df/gamest.h"
#include "df/general_ref.h"
#include "df/general_ref_contained_in_itemst.h"
#include "df/interfacest.h"
#include "df/viewscreen_dwarfmodest.h"
using namespace DFHack;
using namespace df::enums;
using std::map;
using std::queue;
using std::string;
using std::vector;
DFHACK_PLUGIN("confirm");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(game);
REQUIRE_GLOBAL(gps);
typedef std::set<df::interface_key> ikey_set;
command_result df_confirm (color_ostream &out, vector <string> & parameters);
struct conf_wrapper;
static map<string, conf_wrapper*> confirmations;
string active_id;
queue<string> cmds;
namespace DFHack {
DBG_DECLARE(confirm,status);
}
template <typename VT, typename FT>
inline bool in_vector (std::vector<VT> &vec, FT item)
{
return std::find(vec.begin(), vec.end(), item) != vec.end();
}
string char_replace (string s, char a, char b)
{
string res = s;
size_t i = res.size();
while (i--)
if (res[i] == a)
res[i] = b;
return res;
}
bool set_conf_state (string name, bool state);
bool set_conf_paused (string name, bool pause);
class confirmation_base {
public:
bool dirty = false;
enum cstate { INACTIVE, ACTIVE, SELECTED };
virtual string get_id() = 0;
virtual string get_focus_string() = 0;
virtual bool set_state(cstate) = 0;
static bool set_state(string id, cstate state)
{
if (active && active->get_id() == id)
{
active->set_state(state);
return true;
}
return false;
}
protected:
static confirmation_base *active;
};
confirmation_base *confirmation_base::active = nullptr;
struct conf_wrapper {
private:
bool enabled;
bool paused;
std::set<VMethodInterposeLinkBase*> hooks;
public:
conf_wrapper()
:enabled(false),
paused(false)
{}
void add_hook(VMethodInterposeLinkBase *hook)
{
if (!hooks.count(hook))
hooks.insert(hook);
}
bool apply (bool state) {
if (state == enabled)
return true;
for (auto hook : hooks)
{
if (!hook->apply(state))
return false;
}
enabled = state;
return true;
}
bool set_paused (bool pause) {
paused = pause;
return true;
}
inline bool is_enabled() { return enabled; }
inline bool is_paused() { return paused; }
};
namespace trade {
static bool goods_selected (std::vector<uint8_t> &selected)
{
if(!game->main_interface.trade.open)
return false;
for (uint8_t sel : selected)
if (sel == 1)
return true;
return false;
}
inline bool trader_goods_selected ()
{
return goods_selected(game->main_interface.trade.goodflag[0]);
}
inline bool broker_goods_selected ()
{
return goods_selected(game->main_interface.trade.goodflag[1]);
}
/*static bool goods_all_selected(const std::vector<char>& selected, const std::vector<df::item*>& items) \
{
for (size_t i = 0; i < selected.size(); ++i)
{
if (!selected[i])
{
// check to see if item is in a container
// (if the container is not selected, it will be detected separately)
bool in_container = false;
for (auto ref : items[i]->general_refs)
{
if (virtual_cast<df::general_ref_contained_in_itemst>(ref))
{
in_container = true;
break;
}
}
if (!in_container)
return false;
}
}
return true;
}
inline bool trader_goods_all_selected()
{
return goods_all_selected(screen->trader_selected, screen->trader_items);
}
inline bool broker_goods_all_selected()
{
return goods_all_selected(screen->broker_selected, screen->broker_items);
}*/
}
namespace conf_lua {
static color_ostream_proxy *out;
static lua_State *l_state;
bool init (color_ostream &dfout)
{
out = new color_ostream_proxy(Core::getInstance().getConsole());
l_state = Lua::Open(*out);
return l_state;
}
void cleanup()
{
if (out)
{
delete out;
out = nullptr;
}
lua_close(l_state);
}
bool call (const char *func, int nargs = 0, int nres = 0)
{
if (!Lua::PushModulePublic(*out, l_state, "plugins.confirm", func))
return false;
if (nargs > 0)
lua_insert(l_state, lua_gettop(l_state) - nargs);
return Lua::SafeCall(*out, l_state, nargs, nres);
}
bool simple_call (const char *func)
{
Lua::StackUnwinder top(l_state);
return call(func, 0, 0);
}
template <typename T>
void push (T val)
{
Lua::Push(l_state, val);
}
namespace api {
int get_ids (lua_State *L)
{
lua_newtable(L);
for (auto item : confirmations)
Lua::TableInsert(L, item.first, true);
return 1;
}
int get_conf_data (lua_State *L)
{
lua_newtable(L);
int i = 1;
for (auto item : confirmations)
{
Lua::Push(L, i++);
lua_newtable(L);
Lua::TableInsert(L, "id", item.first);
Lua::TableInsert(L, "enabled", item.second->is_enabled());
Lua::TableInsert(L, "paused", item.second->is_paused());
lua_settable(L, -3);
}
return 1;
}
int get_active_id (lua_State *L)
{
if (active_id.size())
Lua::Push(L, active_id);
else
lua_pushnil(L);
return 1;
}
}
}
#define CONF_LUA_FUNC(ns, name) {#name, df::wrap_function(ns::name, true)}
DFHACK_PLUGIN_LUA_FUNCTIONS {
CONF_LUA_FUNC( , set_conf_state),
CONF_LUA_FUNC( , set_conf_paused),
CONF_LUA_FUNC(trade, broker_goods_selected),
//CONF_LUA_FUNC(trade, broker_goods_all_selected),
CONF_LUA_FUNC(trade, trader_goods_selected),
//CONF_LUA_FUNC(trade, trader_goods_all_selected),
DFHACK_LUA_END
};
#define CONF_LUA_CMD(name) {#name, conf_lua::api::name}
DFHACK_PLUGIN_LUA_COMMANDS {
CONF_LUA_CMD(get_ids),
CONF_LUA_CMD(get_conf_data),
CONF_LUA_CMD(get_active_id),
DFHACK_LUA_END
};
void show_options()
{
cmds.push("gui/confirm");
}
template <class T>
class confirmation : public confirmation_base {
public:
typedef T screen_type;
screen_type *screen;
bool set_state (cstate s) override
{
if (confirmation_base::active && confirmation_base::active != this)
{
// Stop this confirmation from appearing over another one
return false;
}
state = s;
dirty = true;
if (s == INACTIVE) {
active_id = "";
confirmation_base::active = nullptr;
}
else {
active_id = get_id();
confirmation_base::active = this;
}
return true;
}
bool feed (ikey_set *input) {
bool mouseExit = false;
if(df::global::enabler->mouse_rbut) {
mouseExit = true;
}
bool mouseSelect = false;
if(df::global::enabler->mouse_lbut) {
mouseSelect = true;
}
conf_wrapper *wrapper = confirmations[this->get_id()];
if(wrapper->is_paused()) {
std::string concernedFocus = this->get_focus_string();
if(!Gui::matchFocusString(this->get_focus_string()))
wrapper->set_paused(false);
return false;
} else if (state == INACTIVE)
{
if(mouseExit) {
if(intercept_key("MOUSE_RIGHT") && set_state(ACTIVE)) {
df::global::enabler->mouse_rbut = 0;
df::global::enabler->mouse_rbut_down = 0;
mouse_pos = df::coord2d(df::global::gps->mouse_x, df::global::gps->mouse_y);
last_key_is_right_click = true;
return true;
}
} else
last_key_is_right_click = false;
if(mouseSelect) {
if(intercept_key("MOUSE_LEFT") && set_state(ACTIVE)) {
df::global::enabler->mouse_lbut = 0;
df::global::enabler->mouse_lbut_down = 0;
mouse_pos = df::coord2d(df::global::gps->mouse_x, df::global::gps->mouse_y);
last_key_is_left_click = true;
return true;
}
} else
last_key_is_left_click = false;
for (df::interface_key key : *input)
{
if (intercept_key(key) && set_state(ACTIVE))
{
last_key = key;
return true;
}
}
return false;
}
else if (state == ACTIVE)
{
if (input->count(df::interface_key::LEAVESCREEN) || mouseExit) {
if(mouseExit) {
df::global::enabler->mouse_rbut = 0;
df::global::enabler->mouse_rbut_down = 0;
}
set_state(INACTIVE);
} else if (input->count(df::interface_key::SELECT))
set_state(SELECTED);
else if (input->count(df::interface_key::CUSTOM_P))
{
DEBUG(status).print("pausing\n");
wrapper->set_paused(true);
set_state(INACTIVE);
}
else if (input->count(df::interface_key::CUSTOM_S))
show_options();
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() {
if (state == ACTIVE)
{
static vector<string> lines;
static const std::string pause_message =
"Pause confirmations until you exit this screen";
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);
split_string(&lines, get_message(), "\n");
size_t max_length = 40;
for (string line : lines)
max_length = std::max(max_length, line.size());
int width = max_length + 4;
vector<string> pause_message_lines;
word_wrap(&pause_message_lines, pause_message, max_length - 3);
int height = lines.size() + pause_message_lines.size() + 5;
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);
string title = ' ' + get_title() + ' ';
Screen::paintString(Screen::Pen(' ', COLOR_DARKGREY, COLOR_BLACK),
x2 - 6, y1, "DFHack");
Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY),
(gps->dimx / 2) - (title.size() / 2), y1, title);
int x = x1 + 2;
int y = y2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN));
OutputString(COLOR_WHITE, x, y, ": Cancel");
x = (gps->dimx - (Screen::getKeyDisplay(df::interface_key::CUSTOM_S) + ": Settings").size()) / 2 + 1;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S));
OutputString(COLOR_WHITE, x, y, ": Settings");
x = x2 - 2 - 3 - Screen::getKeyDisplay(df::interface_key::SELECT).size();
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::SELECT));
OutputString(COLOR_WHITE, x, y, ": 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]);
}
y = y1 + 3 + lines.size();
for (size_t i = 0; i < pause_message_lines.size(); i++)
{
Screen::paintString(Screen::Pen(' ', COLOR_WHITE, COLOR_BLACK), x1 + 5, y + i, pause_message_lines[i]);
}
x = x1 + 2;
OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_P));
OutputString(COLOR_WHITE, x, y, ":");
}
else if (state == SELECTED)
{
ikey_set tmp;
if(last_key_is_left_click) {
long prevx = df::global::gps->mouse_x;
long prevy = df::global::gps->mouse_y;
df::global::gps->mouse_x = mouse_pos.x;
df::global::gps->mouse_y = mouse_pos.y;
df::global::enabler->mouse_lbut = 1;
df::global::enabler->mouse_lbut_down = 1;
screen->feed(&tmp);
df::global::enabler->mouse_lbut = 0;
df::global::enabler->mouse_lbut_down = 0;
df::global::gps->mouse_x = prevx;
df::global::gps->mouse_y = prevy;
}
else if(last_key_is_right_click) {
tmp.insert(df::interface_key::LEAVESCREEN);
screen->feed(&tmp);
}
else {
tmp.insert(last_key);
screen->feed(&tmp);
}
set_state(INACTIVE);
}
if(dirty) {
dirty = false;
df::global::gps->force_full_display_count = 1;
}
}
string get_id() override = 0;
string get_focus_string() override = 0;
#define CONF_LUA_START using namespace conf_lua; Lua::StackUnwinder unwind(l_state); push(screen); push(get_id());
bool intercept_key (df::interface_key key)
{
CONF_LUA_START;
push(key);
if (call("intercept_key", 3, 1))
return lua_toboolean(l_state, -1);
else
return false;
};
bool intercept_key (std::string mouse_button = "MOUSE_LEFT")
{
CONF_LUA_START;
push(mouse_button);
if (call("intercept_key", 3, 1))
return lua_toboolean(l_state, -1);
else
return false;
};
string get_title()
{
CONF_LUA_START;
if (call("get_title", 2, 1) && lua_isstring(l_state, -1))
return lua_tostring(l_state, -1);
else
return "Confirm";
}
string get_message()
{
CONF_LUA_START;
if (call("get_message", 2, 1) && lua_isstring(l_state, -1))
return lua_tostring(l_state, -1);
else
return "<Message generation failed>";
};
UIColor get_color()
{
CONF_LUA_START;
if (call("get_color", 2, 1) && lua_isnumber(l_state, -1))
return lua_tointeger(l_state, -1) % 16;
else
return COLOR_YELLOW;
}
#undef CONF_LUA_START
protected:
cstate state;
df::interface_key last_key;
bool last_key_is_left_click;
bool last_key_is_right_click;
df::coord2d mouse_pos;
};
template<typename T>
int conf_register(confirmation<T> *c, const vector<VMethodInterposeLinkBase*> &hooks)
{
conf_wrapper *w = new conf_wrapper();
confirmations[c->get_id()] = w;
for (auto hook : hooks)
w->add_hook(hook);
return 0;
}
#define IMPLEMENT_CONFIRMATION_HOOKS(cls, prio) \
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(); \
} \
}; \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, feed, prio); \
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cls##_hooks, render, prio); \
static int conf_register_##cls = conf_register(&cls##_instance, {\
&INTERPOSE_HOOK(cls##_hooks, feed), \
&INTERPOSE_HOOK(cls##_hooks, render), \
});
#define DEFINE_CONFIRMATION(cls, screen, focusString) \
class confirmation_##cls : public confirmation<df::screen> { \
virtual string get_id() { static string id = char_replace(#cls, '_', '-'); return id; } \
virtual string get_focus_string() { return focusString; } \
}; \
IMPLEMENT_CONFIRMATION_HOOKS(confirmation_##cls, 0);
/* This section defines stubs for all confirmation dialogs, with methods
implemented in plugins/lua/confirm.lua.
IDs (used in the "confirm enable/disable" command, by Lua, and in the docs)
are obtained by replacing '_' with '-' in the first argument to DEFINE_CONFIRMATION
The second argument to DEFINE_CONFIRMATION determines the viewscreen that any
intercepted input will be fed to.
The third argument to DEFINE_CONFIRMATION determines the focus string that will
be used to determine if the confirmation should be unpaused. If a confirmation is paused
and the focus string is no longer found in the current focus, the confirmation will be
unpaused. Focus strings ending in "*" will use prefix matching e.g. "dwarfmode/Info*" would
match "dwarfmode/Info/Foo", "dwarfmode/Info/Bar" and so on. All matching is case insensitive.
*/
DEFINE_CONFIRMATION(trade_cancel, viewscreen_dwarfmodest, "dwarfmode/Trade");
DEFINE_CONFIRMATION(haul_delete_route, viewscreen_dwarfmodest, "dwarfmode/Hauling");
DEFINE_CONFIRMATION(haul_delete_stop, viewscreen_dwarfmodest, "dwarfmode/Hauling");
DEFINE_CONFIRMATION(depot_remove, viewscreen_dwarfmodest, "dwarfmode/ViewSheets/BUILDING");
DEFINE_CONFIRMATION(squad_disband, viewscreen_dwarfmodest, "dwarfmode/Squads");
DEFINE_CONFIRMATION(order_remove, viewscreen_dwarfmodest, "dwarfmode/Info/WORK_ORDERS");
DEFINE_CONFIRMATION(zone_remove, viewscreen_dwarfmodest, "dwarfmode/Zone");
DEFINE_CONFIRMATION(burrow_remove, viewscreen_dwarfmodest, "dwarfmode/Burrow");
DEFINE_CONFIRMATION(stockpile_remove, viewscreen_dwarfmodest, "dwarfmode/Some/Stockpile");
// these are more complex to implement
//DEFINE_CONFIRMATION(convict, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(trade, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(trade_seize, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(trade_offer, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(trade_select_all, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(uniform_delete, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest);
//DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest);
// locations can't be retired currently
//DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst);
DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands)
{
if (!conf_lua::init(out))
return CR_FAILURE;
commands.push_back(PluginCommand(
"confirm",
"Add confirmation dialogs for destructive actions.",
df_confirm));
return CR_OK;
}
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
is_enabled = enable;
if (is_enabled)
{
conf_lua::simple_call("check");
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
if (plugin_enable(out, false) != CR_OK)
return CR_FAILURE;
conf_lua::cleanup();
for (auto item : confirmations)
{
delete item.second;
}
confirmations.clear();
return CR_OK;
}
DFhackCExport command_result plugin_onupdate (color_ostream &out)
{
while (!cmds.empty())
{
Core::getInstance().runCommand(out, cmds.front());
cmds.pop();
}
return CR_OK;
}
bool set_conf_state (string name, bool state)
{
bool found = false;
for (auto it : confirmations)
{
if (it.first == name)
{
found = true;
it.second->apply(state);
}
}
if (state == false)
{
// dismiss the confirmation too
confirmation_base::set_state(name, confirmation_base::INACTIVE);
}
return found;
}
bool set_conf_paused (string name, bool pause)
{
bool found = false;
for (auto it : confirmations)
{
if (it.first == name)
{
found = true;
it.second->set_paused(pause);
}
}
if (pause == true)
{
// dismiss the confirmation too
confirmation_base::set_state(name, confirmation_base::INACTIVE);
}
return found;
}
void enable_conf (color_ostream &out, string name, bool state)
{
if (!set_conf_state(name, state))
out.printerr("Unrecognized option: %s\n", name.c_str());
}
command_result df_confirm (color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
bool state = true;
if (parameters.empty() || in_vector(parameters, "help") || in_vector(parameters, "status"))
{
out << "Available options: \n";
for (auto it : confirmations)
out.print(" %20s: %s\n", it.first.c_str(), it.second->is_enabled() ? "enabled" : "disabled");
return CR_OK;
}
for (string param : parameters)
{
if (param == "enable")
state = true;
else if (param == "disable")
state = false;
else if (param == "all")
{
for (auto it : confirmations)
it.second->apply(state);
}
else
enable_conf(out, param, state);
}
return CR_OK;
}

@ -1,287 +0,0 @@
local _ENV = mkmodule('plugins.confirm')
local confs = {}
-- Wraps df.interface_key[foo] functionality but fails with invalid keys
keys = {}
setmetatable(keys, {
__index = function(self, k)
return df.interface_key[k] or error('Invalid key: ' .. tostring(k))
end,
__newindex = function() error('Table is read-only') end
})
-- Mouse keys will be sent as a string instead of interface_key
local MOUSE_LEFT = "MOUSE_LEFT"
local MOUSE_RIGHT = "MOUSE_RIGHT"
--[[ The screen where a confirmation has been triggered
Note that this is *not* necessarily the topmost viewscreen, so do not use
gui.getCurViewscreen() or related functions. ]]
--screen = nil
function if_nil(obj, default)
if obj == nil then
return default
else
return obj
end
end
function defconf(id)
if not get_ids()[id] then
error('Bad confirmation ID (not defined in plugin): ' .. id)
end
local cls = {}
cls.intercept_key = function(key) return false end
cls.get_title = function() return if_nil(cls.title, '<No title>') end
cls.get_message = function() return if_nil(cls.message, '<No message>') end
cls.get_color = function() return if_nil(cls.color, COLOR_YELLOW) end
confs[id] = cls
return cls
end
--[[ Beginning of confirmation definitions
All confirmations declared in confirm.cpp must have a corresponding call to
defconf() here, and should implement intercept_key(), get_title(), and
get_message(). get_color() can also be implemented here, but the default should
be sufficient.
In cases where getter functions always return the same value (e.g. get_title()),
they can be replaced with a field named after the method without the "get_"
prefix:
trade.title = "Confirm trade"
is equivalent to:
function trade.get_title() return "Confirm trade" end
]]
trade_cancel = defconf('trade-cancel')
function trade_cancel.intercept_key(key)
return dfhack.gui.matchFocusString("dwarfmode/Trade") and
(key == keys.LEAVESCREEN or key == MOUSE_RIGHT) and
(trader_goods_selected() or broker_goods_selected())
end
trade_cancel.title = "Cancel trade"
trade_cancel.message = "Are you sure you want leave this screen?\nSelected items will not be saved."
haul_delete_route = defconf('haul-delete-route')
function haul_delete_route.intercept_key(key)
return df.global.game.main_interface.current_hover == 180 and key == MOUSE_LEFT
end
haul_delete_route.title = "Confirm deletion"
haul_delete_route.message = "Are you sure you want to delete this route?"
haul_delete_stop = defconf('haul-delete-stop')
function haul_delete_stop.intercept_key(key)
return df.global.game.main_interface.current_hover == 185 and key == MOUSE_LEFT
end
haul_delete_stop.title = "Confirm deletion"
haul_delete_stop.message = "Are you sure you want to delete this stop?"
depot_remove = defconf('depot-remove')
function depot_remove.intercept_key(key)
if df.global.game.main_interface.current_hover == 301 and
key == MOUSE_LEFT and
df.building_tradedepotst:is_instance(dfhack.gui.getSelectedBuilding(true)) then
for _, caravan in pairs(df.global.plotinfo.caravans) do
if caravan.time_remaining > 0 then
return true
end
end
end
end
depot_remove.title = "Confirm depot removal"
depot_remove.message = "Are you sure you want to remove this depot?\n" ..
"Merchants are present and will lose profits."
squad_disband = defconf('squad-disband')
function squad_disband.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 343
end
squad_disband.title = "Disband squad"
squad_disband.message = "Are you sure you want to disband this squad?"
order_remove = defconf('order-remove')
function order_remove.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 222
end
order_remove.title = "Remove manager order"
order_remove.message = "Are you sure you want to remove this order?"
zone_remove = defconf('zone-remove')
function zone_remove.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 130
end
zone_remove.title = "Remove zone"
zone_remove.message = "Are you sure you want to remove this zone?"
burrow_remove = defconf('burrow-remove')
function burrow_remove.intercept_key(key)
return key == MOUSE_LEFT and
(df.global.game.main_interface.current_hover == 171 or
df.global.game.main_interface.current_hover == 168)
end
burrow_remove.title = "Remove burrow"
burrow_remove.message = "Are you sure you want to remove this burrow?"
stockpile_remove = defconf('stockpile-remove')
function stockpile_remove.intercept_key(key)
return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 118
end
stockpile_remove.title = "Remove stockpile"
stockpile_remove.message = "Are you sure you want to remove this stockpile?"
-- these confirmations have more complex button detection requirements
--[[
trade = defconf('trade')
function trade.intercept_key(key)
dfhack.gui.matchFocusString("dwarfmode/Trade") and key == MOUSE_LEFT and hovering over trade button?
end
trade.title = "Confirm trade"
function trade.get_message()
if trader_goods_selected() and broker_goods_selected() then
return "Are you sure you want to trade the selected goods?"
elseif trader_goods_selected() then
return "You are not giving any items. This is likely\n" ..
"to irritate the merchants.\n" ..
"Attempt to trade anyway?"
elseif broker_goods_selected() then
return "You are not receiving any items. You may want to\n" ..
"offer these items instead or choose items to receive.\n" ..
"Attempt to trade anyway?"
else
return "No items are selected. This is likely\n" ..
"to irritate the merchants.\n" ..
"Attempt to trade anyway?"
end
end
trade_seize = defconf('trade-seize')
function trade_seize.intercept_key(key)
return screen.in_edit_count == 0 and
trader_goods_selected() and
key == keys.TRADE_SEIZE
end
trade_seize.title = "Confirm seize"
trade_seize.message = "Are you sure you want to seize these goods?"
trade_offer = defconf('trade-offer')
function trade_offer.intercept_key(key)
return screen.in_edit_count == 0 and
broker_goods_selected() and
key == keys.TRADE_OFFER
end
trade_offer.title = "Confirm offer"
trade_offer.message = "Are you sure you want to offer these goods?\nYou will receive no payment."
trade_select_all = defconf('trade-select-all')
function trade_select_all.intercept_key(key)
if screen.in_edit_count == 0 and key == keys.SEC_SELECT then
if screen.in_right_pane and broker_goods_selected() and not broker_goods_all_selected() then
return true
elseif not screen.in_right_pane and trader_goods_selected() and not trader_goods_all_selected() then
return true
end
end
return false
end
trade_select_all.title = "Confirm selection"
trade_select_all.message = "Selecting all goods will overwrite your current selection\n" ..
"and cannot be undone. Continue?"
uniform_delete = defconf('uniform-delete')
function uniform_delete.intercept_key(key)
return key == keys.D_MILITARY_DELETE_UNIFORM and
screen.page == screen._type.T_page.Uniforms and
#screen.equip.uniforms > 0 and
not screen.equip.in_name_uniform
end
uniform_delete.title = "Delete uniform"
uniform_delete.message = "Are you sure you want to delete this uniform?"
note_delete = defconf('note-delete')
function note_delete.intercept_key(key)
return key == keys.D_NOTE_DELETE and
ui.main.mode == df.ui_sidebar_mode.NotesPoints and
not ui.waypoints.in_edit_name_mode and
not ui.waypoints.in_edit_text_mode
end
note_delete.title = "Delete note"
note_delete.message = "Are you sure you want to delete this note?"
route_delete = defconf('route-delete')
function route_delete.intercept_key(key)
return key == keys.D_NOTE_ROUTE_DELETE and
ui.main.mode == df.ui_sidebar_mode.NotesRoutes and
not ui.waypoints.in_edit_name_mode
end
route_delete.title = "Delete route"
route_delete.message = "Are you sure you want to delete this route?"
convict = defconf('convict')
convict.title = "Confirm conviction"
function convict.intercept_key(key)
return key == keys.SELECT and
screen.cur_column == df.viewscreen_justicest.T_cur_column.ConvictChoices
end
function convict.get_message()
name = dfhack.TranslateName(dfhack.units.getVisibleName(screen.convict_choices[screen.cursor_right]))
if name == "" then
name = "this creature"
end
return "Are you sure you want to convict " .. name .. "?\n" ..
"This action is irreversible."
end
]]--
-- locations cannot be retired currently
--[[
location_retire = defconf('location-retire')
function location_retire.intercept_key(key)
return key == keys.LOCATION_RETIRE and
(screen.menu == df.viewscreen_locationsst.T_menu.Locations or
screen.menu == df.viewscreen_locationsst.T_menu.Occupations) and
screen.in_edit == df.viewscreen_locationsst.T_in_edit.None and
screen.locations[screen.location_idx]
end
location_retire.title = "Retire location"
location_retire.message = "Are you sure you want to retire this location?"
]]--
-- End of confirmation definitions
function check()
local undefined = {}
for id in pairs(get_ids()) do
if not confs[id] then
table.insert(undefined, id)
end
end
if #undefined > 0 then
error('Confirmation definitions missing: ' .. table.concat(undefined, ', '))
end
end
--[[
The C++ plugin invokes methods of individual confirmations through four
functions (corresponding to method names) which receive the relevant screen,
the confirmation ID, and extra arguments in some cases, but these don't have to
do aything unique.
]]
function define_wrapper(name)
_ENV[name] = function(scr, id, ...)
_ENV.screen = scr
if not confs[id] then
error('Bad confirmation ID: ' .. id)
end
return confs[id][name](...)
end
end
define_wrapper('intercept_key')
define_wrapper('get_title')
define_wrapper('get_message')
define_wrapper('get_color')
return _ENV