#include "df/enabler.h" #include "df/viewscreen_adopt_regionst.h" #include "df/viewscreen_choose_game_typest.h" #include "df/viewscreen_choose_start_sitest.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_export_regionst.h" #include "df/viewscreen_game_cleanerst.h" #include "df/viewscreen_initial_prepst.h" #include "df/viewscreen_legendsst.h" #include "df/viewscreen_loadgamest.h" #include "df/viewscreen_new_arenast.h" #include "df/viewscreen_new_regionst.h" #include "df/viewscreen_savegamest.h" #include "df/viewscreen_setupdwarfgamest.h" #include "df/viewscreen_titlest.h" #include "df/viewscreen_update_regionst.h" #include "df/viewscreen_worldst.h" #include "df/world.h" #include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" #include "VTableInterpose.h" #include "modules/Gui.h" #include "modules/Screen.h" using namespace DFHack; DFHACK_PLUGIN("overlay"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(enabler); namespace DFHack { DBG_DECLARE(overlay, control, DebugCategory::LINFO); DBG_DECLARE(overlay, event, DebugCategory::LINFO); } static df::coord2d screenSize; static void call_overlay_lua(color_ostream *out, const char *fn_name, int nargs = 0, int nres = 0, Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA, Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) { DEBUG(event).print("calling overlay lua function: '%s'\n", fn_name); CoreSuspender guard; auto L = Lua::Core::State; Lua::StackUnwinder top(L); if (!out) out = &Core::getInstance().getConsole(); Lua::CallLuaModuleFunction(*out, L, "plugins.overlay", fn_name, nargs, nres, std::forward(args_lambda), std::forward(res_lambda)); } template struct viewscreen_overlay : T { typedef T interpose_base; DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { INTERPOSE_NEXT(logic)(); call_overlay_lua(NULL, "update_viewscreen_widgets", 2, 0, [&](lua_State *L) { Lua::Push(L, T::_identity.getName()); Lua::Push(L, this); }); } DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { bool input_is_handled = false; // don't send input to the overlays if there is a modal dialog up if (!world->status.popups.size()) call_overlay_lua(NULL, "feed_viewscreen_widgets", 3, 1, [&](lua_State *L) { Lua::Push(L, T::_identity.getName()); Lua::Push(L, this); Lua::PushInterfaceKeys(L, Screen::normalize_text_keys(*input)); }, [&](lua_State *L) { input_is_handled = lua_toboolean(L, -1); }); if (!input_is_handled) INTERPOSE_NEXT(feed)(input); else dfhack_lua_viewscreen::markInputAsHandled(); } DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); call_overlay_lua(NULL, "render_viewscreen_widgets", 2, 0, [&](lua_State *L) { Lua::Push(L, T::_identity.getName()); Lua::Push(L, this); }); } }; #define IMPLEMENT_HOOKS(screen) \ typedef viewscreen_overlay screen##_overlay; \ template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(screen##_overlay, logic, 100); \ template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(screen##_overlay, feed, 100); \ template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(screen##_overlay, render, 100); IMPLEMENT_HOOKS(adopt_region) IMPLEMENT_HOOKS(choose_game_type) IMPLEMENT_HOOKS(choose_start_site) IMPLEMENT_HOOKS(dwarfmode) IMPLEMENT_HOOKS(export_region) IMPLEMENT_HOOKS(game_cleaner) IMPLEMENT_HOOKS(initial_prep) IMPLEMENT_HOOKS(legends) IMPLEMENT_HOOKS(loadgame) IMPLEMENT_HOOKS(new_arena) IMPLEMENT_HOOKS(new_region) IMPLEMENT_HOOKS(savegame) IMPLEMENT_HOOKS(setupdwarfgame) IMPLEMENT_HOOKS(title) IMPLEMENT_HOOKS(update_region) IMPLEMENT_HOOKS(world) #undef IMPLEMENT_HOOKS #define INTERPOSE_HOOKS_FAILED(screen) \ !INTERPOSE_HOOK(screen##_overlay, logic).apply(enable) || \ !INTERPOSE_HOOK(screen##_overlay, feed).apply(enable) || \ !INTERPOSE_HOOK(screen##_overlay, render).apply(enable) DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (is_enabled == enable) return CR_OK; if (enable) { screenSize = Screen::getWindowSize(); call_overlay_lua(&out, "rescan"); } DEBUG(control).print("%sing interpose hooks\n", enable ? "enabl" : "disabl"); if (INTERPOSE_HOOKS_FAILED(adopt_region) || INTERPOSE_HOOKS_FAILED(choose_start_site) || INTERPOSE_HOOKS_FAILED(choose_game_type) || INTERPOSE_HOOKS_FAILED(dwarfmode) || INTERPOSE_HOOKS_FAILED(export_region) || INTERPOSE_HOOKS_FAILED(game_cleaner) || INTERPOSE_HOOKS_FAILED(initial_prep) || INTERPOSE_HOOKS_FAILED(legends) || INTERPOSE_HOOKS_FAILED(loadgame) || INTERPOSE_HOOKS_FAILED(new_arena) || INTERPOSE_HOOKS_FAILED(new_region) || INTERPOSE_HOOKS_FAILED(savegame) || INTERPOSE_HOOKS_FAILED(setupdwarfgame) || INTERPOSE_HOOKS_FAILED(title) || INTERPOSE_HOOKS_FAILED(update_region) || INTERPOSE_HOOKS_FAILED(world)) return CR_FAILURE; is_enabled = enable; return CR_OK; } #undef INTERPOSE_HOOKS_FAILED static command_result overlay_cmd(color_ostream &out, std::vector & parameters) { bool show_help = false; call_overlay_lua(&out, "overlay_command", 1, 1, [&](lua_State *L) { Lua::PushVector(L, parameters); }, [&](lua_State *L) { show_help = !lua_toboolean(L, -1); }); return show_help ? CR_WRONG_USAGE : CR_OK; } DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back( PluginCommand( "overlay", "Manage onscreen widgets.", overlay_cmd, Gui::anywhere_hotkey)); return CR_OK; } DFhackCExport command_result plugin_shutdown(color_ostream &out) { return plugin_enable(out, false); } DFhackCExport command_result plugin_onupdate (color_ostream &out) { df::coord2d newScreenSize = Screen::getWindowSize(); if (newScreenSize != screenSize) { call_overlay_lua(&out, "reposition_widgets"); screenSize = newScreenSize; } call_overlay_lua(&out, "update_hotspot_widgets"); return CR_OK; }