diff options
| author | Timothy Collett | 2012-09-10 11:54:56 -0400 |
|---|---|---|
| committer | Timothy Collett | 2012-09-10 11:54:56 -0400 |
| commit | 96abc903abb93b7bf7418f2da3455ee6da5ad942 (patch) | |
| tree | a5326ef1dbd492086b319c888854e707c59d2a56 /library | |
| parent | 274d6038adce5797b58cee78a330eb5d639bf59e (diff) | |
| parent | 21904fd607d0f2037782e28ff7284300663931ab (diff) | |
| download | dfhack-96abc903abb93b7bf7418f2da3455ee6da5ad942.tar.gz dfhack-96abc903abb93b7bf7418f2da3455ee6da5ad942.tar.bz2 dfhack-96abc903abb93b7bf7418f2da3455ee6da5ad942.tar.xz | |
Merge branch 'master' of http://github.com/peterix/dfhack
Diffstat (limited to 'library')
35 files changed, 2843 insertions, 158 deletions
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 75485ff2..109a97e7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -27,6 +27,7 @@ include/Core.h include/ColorText.h include/DataDefs.h include/DataIdentity.h +include/VTableInterpose.h include/LuaWrapper.h include/LuaTools.h include/Error.h @@ -53,6 +54,7 @@ SET(MAIN_SOURCES Core.cpp ColorText.cpp DataDefs.cpp +VTableInterpose.cpp LuaWrapper.cpp LuaTypes.cpp LuaTools.cpp @@ -117,6 +119,7 @@ include/modules/Maps.h include/modules/MapCache.h include/modules/Materials.h include/modules/Notes.h +include/modules/Screen.h include/modules/Translation.h include/modules/Vegetation.h include/modules/Vermin.h @@ -137,6 +140,7 @@ modules/kitchen.cpp modules/Maps.cpp modules/Materials.cpp modules/Notes.cpp +modules/Screen.cpp modules/Translation.cpp modules/Vegetation.cpp modules/Vermin.cpp @@ -330,7 +334,10 @@ install(DIRECTORY lua/ FILES_MATCHING PATTERN "*.lua") install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts - DESTINATION ${DFHACK_DATA_DESTINATION}) + DESTINATION ${DFHACK_DATA_DESTINATION} + FILES_MATCHING PATTERN "*.lua" + PATTERN "*.rb" + ) # Unused for so long that it's not even relevant now... if(BUILD_DEVEL) diff --git a/library/Console-darwin.cpp b/library/Console-darwin.cpp index c547f841..86cd657a 100644 --- a/library/Console-darwin.cpp +++ b/library/Console-darwin.cpp @@ -275,7 +275,7 @@ namespace DFHack /// Reset color to default void reset_color(void) { - color(Console::COLOR_RESET); + color(COLOR_RESET); if(!rawmode) fflush(dfout_C); } diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp index 6b4de736..f32fa1c2 100644 --- a/library/Console-linux.cpp +++ b/library/Console-linux.cpp @@ -62,7 +62,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // George Vulov for MacOSX #ifndef __LINUX__ -#define TEMP_FAILURE_RETRY(expr) \ +#define TMP_FAILURE_RETRY(expr) \ ({ long int _res; \ do _res = (long int) (expr); \ while (_res == -1L && errno == EINTR); \ @@ -155,7 +155,7 @@ namespace DFHack FD_ZERO(&descriptor_set); FD_SET(STDIN_FILENO, &descriptor_set); FD_SET(exit_pipe[0], &descriptor_set); - int ret = TEMP_FAILURE_RETRY( + int ret = TMP_FAILURE_RETRY( select (FD_SETSIZE,&descriptor_set, NULL, NULL, NULL) ); if(ret == -1) @@ -165,7 +165,7 @@ namespace DFHack if (FD_ISSET(STDIN_FILENO, &descriptor_set)) { // read byte from stdin - ret = TEMP_FAILURE_RETRY( + ret = TMP_FAILURE_RETRY( read(STDIN_FILENO, &out, 1) ); if(ret == -1) @@ -245,7 +245,8 @@ namespace DFHack if(rawmode) { const char * clr = "\033c\033[3J\033[H"; - ::write(STDIN_FILENO,clr,strlen(clr)); + if (::write(STDIN_FILENO,clr,strlen(clr)) == -1) + ; } else { @@ -269,13 +270,14 @@ namespace DFHack { const char * colstr = getANSIColor(index); int lstr = strlen(colstr); - ::write(STDIN_FILENO,colstr,lstr); + if (::write(STDIN_FILENO,colstr,lstr) == -1) + ; } } /// Reset color to default void reset_color(void) { - color(Console::COLOR_RESET); + color(COLOR_RESET); if(!rawmode) fflush(dfout_C); } @@ -656,7 +658,8 @@ bool Console::init(bool sharing) inited = false; return false; } - freopen("stdout.log", "w", stdout); + if (!freopen("stdout.log", "w", stdout)) + ; d = new Private(); // make our own weird streams so our IO isn't redirected d->dfout_C = fopen("/dev/tty", "w"); @@ -664,7 +667,8 @@ bool Console::init(bool sharing) clear(); d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO); // init the exit mechanism - pipe(d->exit_pipe); + if (pipe(d->exit_pipe) == -1) + ; FD_ZERO(&d->descriptor_set); FD_SET(STDIN_FILENO, &d->descriptor_set); FD_SET(d->exit_pipe[0], &d->descriptor_set); diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index d4d47303..f0cdda38 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -179,7 +179,7 @@ namespace DFHack void color(int index) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(hConsole, index == color_ostream::COLOR_RESET ? default_attributes : index); + SetConsoleTextAttribute(hConsole, index == COLOR_RESET ? default_attributes : index); } void reset_color( void ) diff --git a/library/Core.cpp b/library/Core.cpp index 09344135..6a0dea7c 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -281,7 +281,7 @@ static command_result runLuaScript(color_ostream &out, std::string name, vector< return ok ? CR_OK : CR_FAILURE; } -static command_result runRubyScript(PluginManager *plug_mgr, std::string name, vector<string> &args) +static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, std::string name, vector<string> &args) { std::string rbcmd = "$script_args = ["; for (size_t i = 0; i < args.size(); i++) @@ -290,7 +290,7 @@ static command_result runRubyScript(PluginManager *plug_mgr, std::string name, v rbcmd += "load './hack/scripts/" + name + ".rb'"; - return plug_mgr->eval_ruby(rbcmd.c_str()); + return plug_mgr->eval_ruby(out, rbcmd.c_str()); } command_result Core::runCommand(color_ostream &out, const std::string &command) @@ -358,7 +358,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve continue; if (pcmd.isHotkeyCommand()) - con.color(Console::COLOR_CYAN); + con.color(COLOR_CYAN); con.print("%s: %s\n",pcmd.name.c_str(), pcmd.description.c_str()); con.reset_color(); if (!pcmd.usage.empty()) @@ -481,7 +481,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve { const PluginCommand & pcmd = (plug->operator[](j)); if (pcmd.isHotkeyCommand()) - con.color(Console::COLOR_CYAN); + con.color(COLOR_CYAN); con.print(" %-22s - %s\n",pcmd.name.c_str(), pcmd.description.c_str()); con.reset_color(); } @@ -519,7 +519,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve for(auto iter = out.begin();iter != out.end();iter++) { if ((*iter).recolor) - con.color(Console::COLOR_CYAN); + con.color(COLOR_CYAN); con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str()); con.reset_color(); } @@ -632,7 +632,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve if (fileExists(filename + ".lua")) res = runLuaScript(con, first, parts); else if (plug_mgr->eval_ruby && fileExists(filename + ".rb")) - res = runRubyScript(plug_mgr, first, parts); + res = runRubyScript(con, plug_mgr, first, parts); else con.printerr("%s is not a recognized command.\n", first.c_str()); } @@ -752,6 +752,7 @@ Core::Core() misc_data_mutex=0; last_world_data_ptr = NULL; last_local_map_ptr = NULL; + last_pause_state = false; top_viewscreen = NULL; screen_window = NULL; server = NULL; @@ -1026,35 +1027,41 @@ int Core::TileUpdate() return true; } -// should always be from simulation thread! -int Core::Update() +int Core::ClaimSuspend(bool force_base) { - if(errorstate) - return -1; + auto tid = this_thread::get_id(); + lock_guard<mutex> lock(d->AccessMutex); - // Pretend this thread has suspended the core in the usual way + if (force_base || d->df_suspend_depth <= 0) { - lock_guard<mutex> lock(d->AccessMutex); - assert(d->df_suspend_depth == 0); - d->df_suspend_thread = this_thread::get_id(); - d->df_suspend_depth = 1000; - } - // Initialize the core - bool first_update = false; - - if(!started) + d->df_suspend_thread = tid; + d->df_suspend_depth = 1000000; + return 1000000; + } + else { - first_update = true; - Init(); - if(errorstate) - return -1; - Lua::Core::Reset(con, "core init"); + assert(d->df_suspend_thread == tid); + return ++d->df_suspend_depth; } +} - color_ostream_proxy out(con); +void Core::DisclaimSuspend(int level) +{ + auto tid = this_thread::get_id(); + lock_guard<mutex> lock(d->AccessMutex); + + assert(d->df_suspend_depth == level && d->df_suspend_thread == tid); + if (level == 1000000) + d->df_suspend_depth = 0; + else + --d->df_suspend_depth; +} + +void Core::doUpdate(color_ostream &out, bool first_update) +{ Lua::Core::Reset(out, "DF code execution"); if (first_update) @@ -1116,18 +1123,48 @@ int Core::Update() } } + if (df::global::pause_state) + { + if (*df::global::pause_state != last_pause_state) + { + onStateChange(out, last_pause_state ? SC_UNPAUSED : SC_PAUSED); + last_pause_state = *df::global::pause_state; + } + } + // Execute per-frame handlers onUpdate(out); - // Release the fake suspend lock + out << std::flush; +} + +// should always be from simulation thread! +int Core::Update() +{ + if(errorstate) + return -1; + + color_ostream_proxy out(con); + + // Pretend this thread has suspended the core in the usual way, + // and run various processing hooks. { - lock_guard<mutex> lock(d->AccessMutex); + CoreSuspendClaimer suspend(true); - assert(d->df_suspend_depth == 1000); - d->df_suspend_depth = 0; - } + // Initialize the core + bool first_update = false; - out << std::flush; + if(!started) + { + first_update = true; + Init(); + if(errorstate) + return -1; + Lua::Core::Reset(con, "core init"); + } + + doUpdate(out, first_update); + } // wake waiting tools // do not allow more tools to join in while we process stuff here @@ -1148,7 +1185,7 @@ int Core::Update() // destroy condition delete nc; // check lua stack depth - Lua::Core::Reset(con, "suspend"); + Lua::Core::Reset(out, "suspend"); } return 0; @@ -1188,6 +1225,7 @@ int Core::Shutdown ( void ) if(errorstate) return true; errorstate = 1; + CoreSuspendClaimer suspend; if(plug_mgr) { delete plug_mgr; @@ -1222,7 +1260,7 @@ bool Core::ncurses_wgetch(int in, int & out) // FIXME: copypasta, push into a method! if(df::global::ui && df::global::gview) { - df::viewscreen * ws = Gui::GetCurrentScreen(); + df::viewscreen * ws = Gui::getCurViewscreen(); if (strict_virtual_cast<df::viewscreen_dwarfmodest>(ws) && df::global::ui->main.mode != ui_sidebar_mode::Hotkeys && df::global::ui->main.hotkeys[idx].cmd == df::ui_hotkey::T_cmd::None) @@ -1538,6 +1576,56 @@ void ClassNameCheck::getKnownClassNames(std::vector<std::string> &names) names.push_back(*it); } +bool Process::patchMemory(void *target, const void* src, size_t count) +{ + uint8_t *sptr = (uint8_t*)target; + uint8_t *eptr = sptr + count; + + // Find the valid memory ranges + std::vector<t_memrange> ranges; + getMemRanges(ranges); + + unsigned start = 0; + while (start < ranges.size() && ranges[start].end <= sptr) + start++; + if (start >= ranges.size() || ranges[start].start > sptr) + return false; + + unsigned end = start+1; + while (end < ranges.size() && ranges[end].start < eptr) + { + if (ranges[end].start != ranges[end-1].end) + return false; + end++; + } + if (ranges[end-1].end < eptr) + return false; + + // Verify current permissions + for (unsigned i = start; i < end; i++) + if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) + return false; + + // Apply writable permissions & update + bool ok = true; + + for (unsigned i = start; i < end && ok; i++) + { + t_memrange perms = ranges[i]; + perms.write = perms.read = true; + if (!setPermisions(perms, perms)) + ok = false; + } + + if (ok) + memmove(target, src, count); + + for (unsigned i = start; i < end && ok; i++) + setPermisions(ranges[i], ranges[i]); + + return ok; +} + /******************************************************************************* M O D U L E S *******************************************************************************/ diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 7f0bacc9..d6604cdb 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -35,6 +35,7 @@ distribution. // must be last due to MS stupidity #include "DataDefs.h" #include "DataIdentity.h" +#include "VTableInterpose.h" #include "MiscUtils.h" @@ -214,6 +215,13 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, { } +virtual_identity::~virtual_identity() +{ + // Remove interpose entries, so that they don't try accessing this object later + for (int i = interpose_list.size()-1; i >= 0; i--) + interpose_list[i]->remove(); +} + /* Vtable name to identity lookup. */ static std::map<std::string, virtual_identity*> name_lookup; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b0a085ec..00d4c517 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -39,6 +39,7 @@ distribution. #include "modules/World.h" #include "modules/Gui.h" +#include "modules/Screen.h" #include "modules/Job.h" #include "modules/Translation.h" #include "modules/Units.h" @@ -84,6 +85,8 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; +using Screen::Pen; + void dfhack_printerr(lua_State *S, const std::string &str); void Lua::Push(lua_State *state, const Units::NoblePosition &pos) @@ -179,6 +182,68 @@ static df::coord CheckCoordXYZ(lua_State *state, int base, bool vararg = false) return p; } +template<class T> +static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int defval) +{ + lua_getfield(L, idx, name); + bool nil = lua_isnil(L, -1); + if (nil) *pf = T(defval); + else if (lua_isnumber(L, -1)) *pf = T(lua_tointeger(L, -1)); + else luaL_error(L, "Field %s is not a number.", name); + lua_pop(L, 1); + return !nil; +} + +static bool get_char_field(lua_State *L, char *pf, int idx, const char *name, char defval) +{ + lua_getfield(L, idx, name); + + if (lua_type(L, -1) == LUA_TSTRING) + { + *pf = lua_tostring(L, -1)[0]; + lua_pop(L, 1); + return true; + } + else + { + lua_pop(L, 1); + return get_int_field(L, pf, idx, name, defval); + } +} + +static void decode_pen(lua_State *L, Pen &pen, int idx) +{ + idx = lua_absindex(L, idx); + + get_char_field(L, &pen.ch, idx, "ch", 0); + + get_int_field(L, &pen.fg, idx, "fg", 7); + get_int_field(L, &pen.bg, idx, "bg", 0); + + lua_getfield(L, idx, "bold"); + if (lua_isnil(L, -1)) + { + pen.bold = (pen.fg & 8) != 0; + pen.fg &= 7; + } + else pen.bold = lua_toboolean(L, -1); + lua_pop(L, 1); + + get_int_field(L, &pen.tile, idx, "tile", 0); + + bool tcolor = get_int_field(L, &pen.tile_fg, idx, "tile_fg", 7); + tcolor = get_int_field(L, &pen.tile_bg, idx, "tile_bg", 0) || tcolor; + + if (tcolor) + pen.tile_mode = Pen::TileColor; + else + { + lua_getfield(L, idx, "tile_color"); + pen.tile_mode = (lua_toboolean(L, -1) ? Pen::CharColor : Pen::AsIs); + lua_pop(L, 1); + } +} + /************************************************** * Per-world persistent configuration storage API * **************************************************/ @@ -684,6 +749,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getFocusString), + WRAPM(Gui, getCurFocus), WRAPM(Gui, getSelectedWorkshopJob), WRAPM(Gui, getSelectedJob), WRAPM(Gui, getSelectedUnit), @@ -749,6 +815,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getAge), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), + WRAPM(Units, getProfessionColor), + WRAPM(Units, getCasteProfessionColor), { NULL, NULL } }; @@ -919,6 +987,7 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) { } static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { + WRAPM(Buildings, setOwner), WRAPM(Buildings, allocInstance), WRAPM(Buildings, checkFreeTiles), WRAPM(Buildings, countExtentTiles), @@ -1019,6 +1088,164 @@ static const luaL_Reg dfhack_constructions_funcs[] = { { NULL, NULL } }; +/***** Screen module *****/ + +static const LuaWrapper::FunctionReg dfhack_screen_module[] = { + WRAPM(Screen, inGraphicsMode), + WRAPM(Screen, clear), + WRAPM(Screen, invalidate), + { NULL, NULL } +}; + +static int screen_getMousePos(lua_State *L) +{ + auto pos = Screen::getMousePos(); + lua_pushinteger(L, pos.x); + lua_pushinteger(L, pos.y); + return 2; +} + +static int screen_getWindowSize(lua_State *L) +{ + auto pos = Screen::getWindowSize(); + lua_pushinteger(L, pos.x); + lua_pushinteger(L, pos.y); + return 2; +} + +static int screen_paintTile(lua_State *L) +{ + Pen pen; + decode_pen(L, pen, 1); + int x = luaL_checkint(L, 2); + int y = luaL_checkint(L, 3); + if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) + { + if (lua_type(L, 4) == LUA_TSTRING) + pen.ch = lua_tostring(L, 4)[0]; + else + pen.ch = luaL_checkint(L, 4); + } + if (lua_gettop(L) >= 5 && !lua_isnil(L, 5)) + pen.tile = luaL_checkint(L, 5); + lua_pushboolean(L, Screen::paintTile(pen, x, y)); + return 1; +} + +static int screen_paintString(lua_State *L) +{ + Pen pen; + decode_pen(L, pen, 1); + int x = luaL_checkint(L, 2); + int y = luaL_checkint(L, 3); + const char *text = luaL_checkstring(L, 4); + lua_pushboolean(L, Screen::paintString(pen, x, y, text)); + return 1; +} + +static int screen_fillRect(lua_State *L) +{ + Pen pen; + decode_pen(L, pen, 1); + int x1 = luaL_checkint(L, 2); + int y1 = luaL_checkint(L, 3); + int x2 = luaL_checkint(L, 4); + int y2 = luaL_checkint(L, 5); + lua_pushboolean(L, Screen::fillRect(pen, x1, y1, x2, y2)); + return 1; +} + +static int screen_findGraphicsTile(lua_State *L) +{ + auto str = luaL_checkstring(L, 1); + int x = luaL_checkint(L, 2); + int y = luaL_checkint(L, 3); + int tile, tile_gs; + if (Screen::findGraphicsTile(str, x, y, &tile, &tile_gs)) + { + lua_pushinteger(L, tile); + lua_pushinteger(L, tile_gs); + return 2; + } + else + { + lua_pushnil(L); + return 1; + } +} + +namespace { + +int screen_show(lua_State *L) +{ + df::viewscreen *before = NULL; + if (lua_gettop(L) >= 2) + before = Lua::CheckDFObject<df::viewscreen>(L, 2); + + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, true); + + bool ok = Screen::show(screen, before); + + // If it is a table, get_pointer created a new object. Don't leak it. + if (!ok && lua_istable(L, 1)) + delete screen; + + lua_pushboolean(L, ok); + return 1; +} + +static int screen_dismiss(lua_State *L) +{ + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); + Screen::dismiss(screen); + return 0; +} + +static int screen_isDismissed(lua_State *L) +{ + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); + lua_pushboolean(L, Screen::isDismissed(screen)); + return 1; +} + +static int screen_doSimulateInput(lua_State *L) +{ + auto screen = Lua::CheckDFObject<df::viewscreen>(L, 1); + luaL_checktype(L, 2, LUA_TTABLE); + + if (!screen) + luaL_argerror(L, 1, "NULL screen"); + + int sz = lua_rawlen(L, 2); + std::set<df::interface_key> keys; + + for (int j = 1; j <= sz; j++) + { + lua_rawgeti(L, 2, j); + keys.insert((df::interface_key)lua_tointeger(L, -1)); + lua_pop(L, 1); + } + + screen->feed(&keys); + return 0; +} + +} + +static const luaL_Reg dfhack_screen_funcs[] = { + { "getMousePos", screen_getMousePos }, + { "getWindowSize", screen_getWindowSize }, + { "paintTile", screen_paintTile }, + { "paintString", screen_paintString }, + { "fillRect", screen_fillRect }, + { "findGraphicsTile", screen_findGraphicsTile }, + { "show", &Lua::CallWithCatchWrapper<screen_show> }, + { "dismiss", screen_dismiss }, + { "isDismissed", screen_isDismissed }, + { "_doSimulateInput", screen_doSimulateInput }, + { NULL, NULL } +}; + /***** Internal module *****/ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) @@ -1124,6 +1351,17 @@ static int internal_getMemRanges(lua_State *L) return 1; } +static int internal_patchMemory(lua_State *L) +{ + void *dest = checkaddr(L, 1); + void *src = checkaddr(L, 2); + int size = luaL_checkint(L, 3); + if (size < 0) luaL_argerror(L, 1, "negative size"); + bool ok = Core::getInstance().p->patchMemory(dest, src, size); + lua_pushboolean(L, ok); + return 1; +} + static int internal_memmove(lua_State *L) { void *dest = checkaddr(L, 1); @@ -1214,6 +1452,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "setAddress", internal_setAddress }, { "getVTable", internal_getVTable }, { "getMemRanges", internal_getMemRanges }, + { "patchMemory", internal_patchMemory }, { "memmove", internal_memmove }, { "memcmp", internal_memcmp }, { "memscan", internal_memscan }, @@ -1240,5 +1479,6 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs); OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs); OpenModule(state, "constructions", dfhack_constructions_module); + OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); } diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 28571a0f..7c2c8f8d 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -68,6 +68,11 @@ lua_State *DFHack::Lua::Core::State = NULL; void dfhack_printerr(lua_State *S, const std::string &str); +inline bool is_null_userdata(lua_State *L, int idx) +{ + return lua_islightuserdata(L, idx) && !lua_touserdata(L, idx); +} + inline void AssertCoreSuspend(lua_State *state) { assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended()); @@ -252,7 +257,7 @@ static int lua_dfhack_color(lua_State *S) { int cv = luaL_optint(S, 1, -1); - if (cv < -1 || cv > color_ostream::COLOR_MAX) + if (cv < -1 || cv > COLOR_MAX) luaL_argerror(S, 1, "invalid color value"); color_ostream *out = Lua::GetOutput(S); @@ -1244,14 +1249,123 @@ static const luaL_Reg dfhack_coro_funcs[] = { static int DFHACK_EVENT_META_TOKEN = 0; -int DFHack::Lua::NewEvent(lua_State *state) +namespace { + struct EventObject { + int item_count; + Lua::Event::Owner *owner; + }; +} + +void DFHack::Lua::Event::New(lua_State *state, Owner *owner) { - lua_newtable(state); + auto obj = (EventObject *)lua_newuserdata(state, sizeof(EventObject)); + obj->item_count = 0; + obj->owner = owner; + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN); lua_setmetatable(state, -2); + lua_newtable(state); + lua_setuservalue(state, -2); +} + +void DFHack::Lua::Event::SetPrivateCallback(lua_State *L, int event) +{ + lua_getuservalue(L, event); + lua_swap(L); + lua_rawsetp(L, -2, NULL); + lua_pop(L, 1); +} + +static int dfhack_event_new(lua_State *L) +{ + Lua::Event::New(L); return 1; } +static int dfhack_event_len(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + auto obj = (EventObject *)lua_touserdata(L, 1); + lua_pushinteger(L, obj->item_count); + return 1; +} + +static int dfhack_event_tostring(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + auto obj = (EventObject *)lua_touserdata(L, 1); + lua_pushfstring(L, "<event: %d listeners>", obj->item_count); + return 1; +} + +static int dfhack_event_index(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + return 1; +} + +static int dfhack_event_next(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + lua_getuservalue(L, 1); + lua_pushvalue(L, 2); + while (lua_next(L, -2)) + { + if (is_null_userdata(L, -2)) + lua_pop(L, 1); + else + return 2; + } + lua_pushnil(L); + return 1; +} + +static int dfhack_event_pairs(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + lua_pushcfunction(L, dfhack_event_next); + lua_pushvalue(L, 1); + lua_pushnil(L); + return 3; +} + +static int dfhack_event_newindex(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + if (is_null_userdata(L, 2)) + luaL_argerror(L, 2, "Key NULL is reserved in events."); + + lua_settop(L, 3); + lua_getuservalue(L, 1); + bool new_nil = lua_isnil(L, 3); + + lua_pushvalue(L, 2); + lua_rawget(L, 4); + bool old_nil = lua_isnil(L, -1); + lua_settop(L, 4); + + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + lua_rawset(L, 4); + + int delta = 0; + if (old_nil && !new_nil) delta = 1; + else if (new_nil && !old_nil) delta = -1; + + if (delta != 0) + { + auto obj = (EventObject *)lua_touserdata(L, 1); + obj->item_count += delta; + if (obj->owner) + obj->owner->on_count_changed(obj->item_count, delta); + } + + return 0; +} + static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfun) { for (int i = 0; i < num_args; i++) @@ -1292,7 +1406,7 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c) while (lua_next(L, event)) { // Skip the NULL key in the main loop - if (lua_islightuserdata(L, -2) && !lua_touserdata(L, -2)) + if (is_null_userdata(L, -2)) lua_pop(L, 1); else do_invoke_event(L, argbase, num_args, errorfun); @@ -1303,14 +1417,20 @@ static void dfhack_event_invoke(lua_State *L, int base, bool from_c) static int dfhack_event_call(lua_State *state) { - luaL_checktype(state, 1, LUA_TTABLE); + luaL_checktype(state, 1, LUA_TUSERDATA); luaL_checkstack(state, lua_gettop(state)+2, "stack overflow in event dispatch"); + auto obj = (EventObject *)lua_touserdata(state, 1); + if (obj->owner) + obj->owner->on_invoked(state, lua_gettop(state)-1, false); + + lua_getuservalue(state, 1); + lua_replace(state, 1); dfhack_event_invoke(state, 0, false); return 0; } -void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args) +void DFHack::Lua::Event::Invoke(color_ostream &out, lua_State *state, void *key, int num_args) { AssertCoreSuspend(state); @@ -1325,7 +1445,7 @@ void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, i lua_rawgetp(state, LUA_REGISTRYINDEX, key); - if (!lua_istable(state, -1)) + if (!lua_isuserdata(state, -1)) { if (!lua_isnil(state, -1)) out.printerr("Invalid event object in Lua::InvokeEvent"); @@ -1333,22 +1453,29 @@ void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, i return; } + auto obj = (EventObject *)lua_touserdata(state, -1); lua_insert(state, base+1); + if (obj->owner) + obj->owner->on_invoked(state, num_args, true); + + lua_getuservalue(state, base+1); + lua_replace(state, base+1); + color_ostream *cur_out = Lua::GetOutput(state); set_dfhack_output(state, &out); dfhack_event_invoke(state, base, true); set_dfhack_output(state, cur_out); } -void DFHack::Lua::MakeEvent(lua_State *state, void *key) +void DFHack::Lua::Event::Make(lua_State *state, void *key, Owner *owner) { lua_rawgetp(state, LUA_REGISTRYINDEX, key); if (lua_isnil(state, -1)) { lua_pop(state, 1); - NewEvent(state); + New(state, owner); } lua_dup(state); @@ -1358,7 +1485,7 @@ void DFHack::Lua::MakeEvent(lua_State *state, void *key) void DFHack::Lua::Notification::invoke(color_ostream &out, int nargs) { assert(state); - InvokeEvent(out, state, key, nargs); + Event::Invoke(out, state, key, nargs); } void DFHack::Lua::Notification::bind(lua_State *state, void *key) @@ -1369,12 +1496,12 @@ void DFHack::Lua::Notification::bind(lua_State *state, void *key) void DFHack::Lua::Notification::bind(lua_State *state, const char *name) { - MakeEvent(state, this); + Event::Make(state, this); if (handler) { PushFunctionWrapper(state, 0, name, handler); - lua_rawsetp(state, -2, NULL); + Event::SetPrivateCallback(state, -2); } this->state = state; @@ -1435,11 +1562,26 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_newtable(state); lua_pushcfunction(state, dfhack_event_call); lua_setfield(state, -2, "__call"); - lua_pushcfunction(state, Lua::NewEvent); - lua_setfield(state, -2, "new"); + lua_pushcfunction(state, dfhack_event_len); + lua_setfield(state, -2, "__len"); + lua_pushcfunction(state, dfhack_event_tostring); + lua_setfield(state, -2, "__tostring"); + lua_pushcfunction(state, dfhack_event_index); + lua_setfield(state, -2, "__index"); + lua_pushcfunction(state, dfhack_event_newindex); + lua_setfield(state, -2, "__newindex"); + lua_pushcfunction(state, dfhack_event_pairs); + lua_setfield(state, -2, "__pairs"); lua_dup(state); lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN); - lua_setfield(state, -2, "event"); + + lua_newtable(state); + lua_pushcfunction(state, dfhack_event_new); + lua_setfield(state, -2, "new"); + lua_dup(state); + lua_setfield(state, -3, "__metatable"); + lua_setfield(state, -3, "event"); + lua_pop(state, 1); // Initialize the dfhack global luaL_setfuncs(state, dfhack_funcs, 0); @@ -1599,7 +1741,7 @@ void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) { } Lua::Push(State, code); - Lua::InvokeEvent(out, State, (void*)onStateChange, 1); + Lua::Event::Invoke(out, State, (void*)onStateChange, 1); } static void run_timers(color_ostream &out, lua_State *L, @@ -1653,7 +1795,7 @@ void DFHack::Lua::Core::Init(color_ostream &out) // Register events lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); - MakeEvent(State, (void*)onStateChange); + Event::Make(State, (void*)onStateChange); lua_setfield(State, -2, "onStateChange"); lua_pushcfunction(State, dfhack_timeout); diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 8548c5d0..e7197796 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -37,6 +37,7 @@ distribution. #include "DataDefs.h" #include "DataIdentity.h" #include "LuaWrapper.h" +#include "LuaTools.h" #include "DataFuncs.h" #include "MiscUtils.h" @@ -285,6 +286,9 @@ void container_identity::lua_item_read(lua_State *state, int fname_idx, void *pt void container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) { + if (is_readonly()) + field_error(state, fname_idx, "container is read-only", "write"); + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); void *pitem = item_pointer(id, ptr, idx); id->lua_write(state, fname_idx, pitem, val_index); @@ -1064,6 +1068,27 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id return 1; } +int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *context) +{ + if (!context) + context = "native code"; + + try { + return fn(state); + } + catch (Error::NullPointer &e) { + const char *vn = e.varname(); + return luaL_error(state, "%s: NULL pointer: %s", context, vn ? vn : "?"); + } + catch (Error::InvalidArgument &e) { + const char *vn = e.expr(); + return luaL_error(state, "%s: Invalid argument; expected: %s", context, vn ? vn : "?"); + } + catch (std::exception &e) { + return luaL_error(state, "%s: C++ exception: %s", context, e.what()); + } +} + /** * Push a closure invoking the given function. */ diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a314883e..d8b9ff27 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -108,6 +108,50 @@ struct Plugin::RefAutoinc ~RefAutoinc(){ lock->lock_sub(); }; }; +struct Plugin::LuaCommand { + Plugin *owner; + std::string name; + int (*command)(lua_State *state); + + LuaCommand(Plugin *owner, std::string name) + : owner(owner), name(name), command(NULL) {} +}; + +struct Plugin::LuaFunction { + Plugin *owner; + std::string name; + function_identity_base *identity; + bool silent; + + LuaFunction(Plugin *owner, std::string name) + : owner(owner), name(name), identity(NULL), silent(false) {} +}; + +struct Plugin::LuaEvent : public Lua::Event::Owner { + LuaFunction handler; + Lua::Notification *event; + bool active; + int count; + + LuaEvent(Plugin *owner, std::string name) + : handler(owner,name), event(NULL), active(false), count(0) + { + handler.silent = true; + } + + void on_count_changed(int new_cnt, int delta) { + RefAutoinc lock(handler.owner->access); + count = new_cnt; + if (event) + event->on_count_changed(new_cnt, delta); + } + void on_invoked(lua_State *state, int nargs, bool from_c) { + RefAutoinc lock(handler.owner->access); + if (event) + event->on_invoked(state, nargs, from_c); + } +}; + Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _filename, PluginManager * pm) { filename = filepath; @@ -151,6 +195,9 @@ bool Plugin::load(color_ostream &con) { return true; } + // enter suspend + CoreSuspender suspend; + // open the library, etc DFLibrary * plug = OpenPlugin(filename.c_str()); if(!plug) { @@ -188,7 +235,7 @@ bool Plugin::load(color_ostream &con) plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown"); plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange"); plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect"); - plugin_eval_ruby = (command_result (*)(const char*)) LookupPlugin(plug, "plugin_eval_ruby"); + plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); index_lua(plug); this->name = *plug_name; plugin_lib = plug; @@ -226,6 +273,8 @@ bool Plugin::unload(color_ostream &con) } // wait for all calls to finish access->wait(); + // enter suspend + CoreSuspender suspend; // notify plugin about shutdown, if it has a shutdown function command_result cr = CR_OK; if(plugin_shutdown) @@ -439,7 +488,11 @@ void Plugin::index_lua(DFLibrary *lib) cmd->handler.identity = evlist->event->get_handler(); cmd->event = evlist->event; if (cmd->active) + { cmd->event->bind(Lua::Core::State, cmd); + if (cmd->count > 0) + cmd->event->on_count_changed(cmd->count, 0); + } } } } @@ -467,7 +520,7 @@ int Plugin::lua_cmd_wrapper(lua_State *state) luaL_error(state, "plugin command %s() has been unloaded", (cmd->owner->name+"."+cmd->name).c_str()); - return cmd->command(state); + return Lua::CallWithCatch(state, cmd->command, cmd->name.c_str()); } int Plugin::lua_fun_wrapper(lua_State *state) @@ -477,8 +530,13 @@ int Plugin::lua_fun_wrapper(lua_State *state) RefAutoinc lock(cmd->owner->access); if (!cmd->identity) + { + if (cmd->silent) + return 0; + luaL_error(state, "plugin function %s() has been unloaded", (cmd->owner->name+"."+cmd->name).c_str()); + } return LuaWrapper::method_wrapper_core(state, cmd->identity); } @@ -506,14 +564,14 @@ void Plugin::open_lua(lua_State *state, int table) { for (auto it = lua_events.begin(); it != lua_events.end(); ++it) { - Lua::MakeEvent(state, it->second); + Lua::Event::Make(state, it->second, it->second); push_function(state, &it->second->handler); - lua_rawsetp(state, -2, NULL); + Lua::Event::SetPrivateCallback(state, -2); it->second->active = true; if (it->second->event) - it->second->event->bind(state, it->second); + it->second->event->bind(Lua::Core::State, it->second); lua_setfield(state, table, it->first.c_str()); } diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp new file mode 100644 index 00000000..3725ccba --- /dev/null +++ b/library/VTableInterpose.cpp @@ -0,0 +1,249 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" + +#include <string> +#include <vector> +#include <map> + +#include "MemAccess.h" +#include "Core.h" +#include "VersionInfo.h" +#include "VTableInterpose.h" + +#include "MiscUtils.h" + +using namespace DFHack; + +/* + * Code for accessing method pointers directly. Very compiler-specific. + */ + +#if defined(_MSC_VER) + +struct MSVC_MPTR { + void *method; + intptr_t this_shift; +}; + +bool DFHack::is_vmethod_pointer_(void *pptr) +{ + auto pobj = (MSVC_MPTR*)pptr; + if (!pobj->method) return false; + + // MSVC implements pointers to vmethods via thunks. + // This expects that they all follow a very specific pattern. + auto pval = (unsigned*)pobj->method; + switch (pval[0]) { + case 0x20FF018BU: // mov eax, [ecx]; jmp [eax] + case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??] + case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????] + return true; + default: + return false; + } +} + +int DFHack::vmethod_pointer_to_idx_(void *pptr) +{ + auto pobj = (MSVC_MPTR*)pptr; + if (!pobj->method || pobj->this_shift != 0) return -1; + + auto pval = (unsigned*)pobj->method; + switch (pval[0]) { + case 0x20FF018BU: // mov eax, [ecx]; jmp [eax] + return 0; + case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??] + return ((int8_t)pval[1])/sizeof(void*); + case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????] + return ((int32_t)pval[1])/sizeof(void*); + default: + return -1; + } +} + +void* DFHack::method_pointer_to_addr_(void *pptr) +{ + if (is_vmethod_pointer_(pptr)) return NULL; + auto pobj = (MSVC_MPTR*)pptr; + return pobj->method; +} + +void DFHack::addr_to_method_pointer_(void *pptr, void *addr) +{ + auto pobj = (MSVC_MPTR*)pptr; + pobj->method = addr; + pobj->this_shift = 0; +} + +#elif defined(__GXX_ABI_VERSION) + +struct GCC_MPTR { + intptr_t method; + intptr_t this_shift; +}; + +bool DFHack::is_vmethod_pointer_(void *pptr) +{ + auto pobj = (GCC_MPTR*)pptr; + return (pobj->method & 1) != 0; +} + +int DFHack::vmethod_pointer_to_idx_(void *pptr) +{ + auto pobj = (GCC_MPTR*)pptr; + if ((pobj->method & 1) == 0 || pobj->this_shift != 0) + return -1; + return (pobj->method-1)/sizeof(void*); +} + +void* DFHack::method_pointer_to_addr_(void *pptr) +{ + auto pobj = (GCC_MPTR*)pptr; + if ((pobj->method & 1) != 0 || pobj->this_shift != 0) + return NULL; + return (void*)pobj->method; +} + +void DFHack::addr_to_method_pointer_(void *pptr, void *addr) +{ + auto pobj = (GCC_MPTR*)pptr; + pobj->method = (intptr_t)addr; + pobj->this_shift = 0; +} + +#else +#error Unknown compiler type +#endif + +void *virtual_identity::get_vmethod_ptr(int idx) +{ + assert(idx >= 0); + void **vtable = (void**)vtable_ptr; + if (!vtable) return NULL; + return vtable[idx]; +} + +bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) +{ + assert(idx >= 0); + void **vtable = (void**)vtable_ptr; + if (!vtable) return NULL; + return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); +} + +void VMethodInterposeLinkBase::set_chain(void *chain) +{ + saved_chain = chain; + addr_to_method_pointer_(chain_mptr, chain); +} + +VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr) + : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr), + saved_chain(NULL), next(NULL), prev(NULL) +{ + if (vmethod_idx < 0 || interpose_method == NULL) + { + fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n", + vmethod_idx, unsigned(interpose_method)); + fflush(stderr); + abort(); + } +} + +VMethodInterposeLinkBase::~VMethodInterposeLinkBase() +{ + if (is_applied()) + remove(); +} + +bool VMethodInterposeLinkBase::apply() +{ + if (is_applied()) + return true; + if (!host->vtable_ptr) + return false; + + // Retrieve the current vtable entry + void *old_ptr = host->get_vmethod_ptr(vmethod_idx); + assert(old_ptr != NULL); + + // Check if there are other interpose entries for the same slot + VMethodInterposeLinkBase *old_link = NULL; + + for (int i = host->interpose_list.size()-1; i >= 0; i--) + { + if (host->interpose_list[i]->vmethod_idx != vmethod_idx) + continue; + + old_link = host->interpose_list[i]; + assert(old_link->next == NULL && old_ptr == old_link->interpose_method); + break; + } + + // Apply the new method ptr + if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) + return false; + + set_chain(old_ptr); + host->interpose_list.push_back(this); + + // Link into the chain if any + if (old_link) + { + old_link->next = this; + prev = old_link; + } + + return true; +} + +void VMethodInterposeLinkBase::remove() +{ + if (!is_applied()) + return; + + // Remove from the list in the identity + for (int i = host->interpose_list.size()-1; i >= 0; i--) + if (host->interpose_list[i] == this) + vector_erase_at(host->interpose_list, i); + + // Remove from the chain + if (prev) + prev->next = next; + + if (next) + { + next->set_chain(saved_chain); + next->prev = prev; + } + else + { + host->set_vmethod_ptr(vmethod_idx, saved_chain); + } + + prev = next = NULL; + set_chain(NULL); +} diff --git a/library/include/ColorText.h b/library/include/ColorText.h index 0cc286dc..50d1f362 100644 --- a/library/include/ColorText.h +++ b/library/include/ColorText.h @@ -41,30 +41,32 @@ namespace dfproto namespace DFHack { + enum color_value + { + COLOR_RESET = -1, + COLOR_BLACK = 0, + COLOR_BLUE, + COLOR_GREEN, + COLOR_CYAN, + COLOR_RED, + COLOR_MAGENTA, + COLOR_BROWN, + COLOR_GREY, + COLOR_DARKGREY, + COLOR_LIGHTBLUE, + COLOR_LIGHTGREEN, + COLOR_LIGHTCYAN, + COLOR_LIGHTRED, + COLOR_LIGHTMAGENTA, + COLOR_YELLOW, + COLOR_WHITE, + COLOR_MAX = COLOR_WHITE + }; + class DFHACK_EXPORT color_ostream : public std::ostream { public: - enum color_value - { - COLOR_RESET = -1, - COLOR_BLACK = 0, - COLOR_BLUE, - COLOR_GREEN, - COLOR_CYAN, - COLOR_RED, - COLOR_MAGENTA, - COLOR_BROWN, - COLOR_GREY, - COLOR_DARKGREY, - COLOR_LIGHTBLUE, - COLOR_LIGHTGREEN, - COLOR_LIGHTCYAN, - COLOR_LIGHTRED, - COLOR_LIGHTMAGENTA, - COLOR_YELLOW, - COLOR_WHITE, - COLOR_MAX = COLOR_WHITE - }; + typedef DFHack::color_value color_value; private: color_value cur_color; diff --git a/library/include/Core.h b/library/include/Core.h index 653298d8..e1f1cf3f 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -75,7 +75,9 @@ namespace DFHack SC_MAP_UNLOADED = 3, SC_VIEWSCREEN_CHANGED = 4, SC_CORE_INITIALIZED = 5, - SC_BEGIN_UNLOAD = 6 + SC_BEGIN_UNLOAD = 6, + SC_PAUSED = 7, + SC_UNPAUSED = 8 }; // Core is a singleton. Why? Because it is closely tied to SDL calls. It tracks the global state of DF. @@ -172,6 +174,10 @@ namespace DFHack struct Private; Private *d; + friend class CoreSuspendClaimer; + int ClaimSuspend(bool force_base); + void DisclaimSuspend(int level); + bool Init(); int Update (void); int TileUpdate (void); @@ -179,6 +185,7 @@ namespace DFHack int DFH_SDL_Event(SDL::Event* event); bool ncurses_wgetch(int in, int & out); + void doUpdate(color_ostream &out, bool first_update); void onUpdate(color_ostream &out); void onStateChange(color_ostream &out, state_change_event event); @@ -228,6 +235,7 @@ namespace DFHack // for state change tracking void *last_local_map_ptr; df::viewscreen *top_viewscreen; + bool last_pause_state; // Very important! bool started; @@ -246,4 +254,20 @@ namespace DFHack CoreSuspender(Core *core) : core(core) { core->Suspend(); } ~CoreSuspender() { core->Resume(); } }; + + /** Claims the current thread already has the suspend lock. + * Strictly for use in callbacks from DF. + */ + class CoreSuspendClaimer { + Core *core; + int level; + public: + CoreSuspendClaimer(bool base = false) : core(&Core::getInstance()) { + level = core->ClaimSuspend(base); + } + CoreSuspendClaimer(Core *core, bool base = false) : core(core) { + level = core->ClaimSuspend(base); + } + ~CoreSuspendClaimer() { core->DisclaimSuspend(level); } + }; } diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 7f4d94c8..6b3aeb3e 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -28,6 +28,7 @@ distribution. #include <sstream> #include <vector> #include <map> +#include <set> #include "Core.h" #include "BitArray.h" @@ -292,6 +293,8 @@ namespace DFHack typedef virtual_class *virtual_ptr; #endif + class DFHACK_EXPORT VMethodInterposeLinkBase; + class DFHACK_EXPORT virtual_identity : public struct_identity { static std::map<void*, virtual_identity*> known; @@ -299,6 +302,9 @@ namespace DFHack void *vtable_ptr; + friend class VMethodInterposeLinkBase; + std::vector<VMethodInterposeLinkBase*> interpose_list; + protected: virtual void doInit(Core *core); @@ -306,10 +312,14 @@ namespace DFHack bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } + void *get_vmethod_ptr(int index); + bool set_vmethod_ptr(int index, void *ptr); + public: virtual_identity(size_t size, TAllocateFn alloc, const char *dfhack_name, const char *original_name, virtual_identity *parent, const struct_field_info *fields); + ~virtual_identity(); virtual identity_type type() { return IDTYPE_CLASS; } @@ -337,6 +347,8 @@ namespace DFHack : (this == get(instance_ptr)); } + template<class P> static P get_vmethod_ptr(P selector); + public: bool can_instantiate() { return can_allocate(); } virtual_ptr instantiate() { return can_instantiate() ? (virtual_ptr)do_allocate() : NULL; } diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 637a532f..52039566 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -75,28 +75,31 @@ namespace df { cur_lua_ostream_argument name(state); #define INSTANTIATE_RETURN_TYPE(FArgs) \ - template<FW_TARGSC class RT> struct return_type<RT (*) FArgs> { typedef RT type; }; \ - template<FW_TARGSC class RT, class CT> struct return_type<RT (CT::*) FArgs> { typedef RT type; }; + template<FW_TARGSC class RT> struct return_type<RT (*) FArgs> { \ + typedef RT type; \ + static const bool is_method = false; \ + }; \ + template<FW_TARGSC class RT, class CT> struct return_type<RT (CT::*) FArgs> { \ + typedef RT type; \ + typedef CT class_type; \ + static const bool is_method = true; \ + }; #define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ template<FW_TARGS> struct function_wrapper<void (*) FArgs, true> { \ - static const bool is_method = false; \ static const int num_args = Count; \ static void execute(lua_State *state, int base, void (*cb) FArgs) { Loads; INVOKE_VOID(cb Args); } \ }; \ template<FW_TARGSC class RT> struct function_wrapper<RT (*) FArgs, false> { \ - static const bool is_method = false; \ static const int num_args = Count; \ static void execute(lua_State *state, int base, RT (*cb) FArgs) { Loads; INVOKE_RV(cb Args); } \ }; \ template<FW_TARGSC class CT> struct function_wrapper<void (CT::*) FArgs, true> { \ - static const bool is_method = true; \ static const int num_args = Count+1; \ static void execute(lua_State *state, int base, void (CT::*cb) FArgs) { \ LOAD_CLASS() Loads; INVOKE_VOID((self->*cb) Args); } \ }; \ template<FW_TARGSC class RT, class CT> struct function_wrapper<RT (CT::*) FArgs, false> { \ - static const bool is_method = true; \ static const int num_args = Count+1; \ static void execute(lua_State *state, int base, RT (CT::*cb) FArgs) { \ LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \ diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index dcd0ae97..0f5fd9e7 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -115,6 +115,8 @@ namespace DFHack virtual void lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx); virtual void lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index); + virtual bool is_readonly() { return false; } + virtual bool resize(void *ptr, int size) { return false; } virtual bool erase(void *ptr, int index) { return false; } virtual bool insert(void *ptr, int index, void *pitem) { return false; } @@ -343,6 +345,33 @@ namespace df } }; + template<class T> + class ro_stl_container_identity : public container_identity { + const char *name; + + public: + ro_stl_container_identity(const char *name, type_identity *item, enum_identity *ienum = NULL) + : container_identity(sizeof(T), &allocator_fn<T>, item, ienum), name(name) + {} + + std::string getFullName(type_identity *item) { + return name + container_identity::getFullName(item); + } + + virtual bool is_readonly() { return true; } + virtual bool resize(void *ptr, int size) { return false; } + virtual bool erase(void *ptr, int size) { return false; } + virtual bool insert(void *ptr, int idx, void *item) { return false; } + + protected: + virtual int item_count(void *ptr, CountMode) { return ((T*)ptr)->size(); } + virtual void *item_pointer(type_identity *item, void *ptr, int idx) { + auto iter = (*(T*)ptr).begin(); + for (; idx > 0; idx--) ++iter; + return (void*)&*iter; + } + }; + class bit_array_identity : public bit_container_identity { public: /* @@ -517,6 +546,10 @@ namespace df static container_identity *get(); }; + template<class T> struct identity_traits<std::set<T> > { + static container_identity *get(); + }; + template<> struct identity_traits<BitArray<int> > { static bit_array_identity identity; static bit_container_identity *get() { return &identity; } @@ -580,6 +613,13 @@ namespace df } template<class T> + inline container_identity *identity_traits<std::set<T> >::get() { + typedef std::set<T> container; + static ro_stl_container_identity<container> identity("set", identity_traits<T>::get()); + return &identity; + } + + template<class T> inline bit_container_identity *identity_traits<BitArray<T> >::get() { static bit_array_identity identity(identity_traits<T>::get()); return &identity; diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index d3c7a65d..6b1afb88 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -192,6 +192,16 @@ namespace DFHack {namespace Lua { } /** + * Call through to the function with try/catch for C++ exceptions. + */ + DFHACK_EXPORT int CallWithCatch(lua_State *, int (*fn)(lua_State*), const char *context = NULL); + + template<int (*cb)(lua_State*)> + int CallWithCatchWrapper(lua_State *state) { + return CallWithCatch(state, cb); + } + + /** * Invoke lua function via pcall. Returns true if success. * If an error is signalled, and perr is true, it is printed and popped from the stack. */ @@ -300,9 +310,18 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT bool IsCoreContext(lua_State *state); - DFHACK_EXPORT int NewEvent(lua_State *state); - DFHACK_EXPORT void MakeEvent(lua_State *state, void *key); - DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args); + namespace Event { + struct DFHACK_EXPORT Owner { + virtual ~Owner() {} + virtual void on_count_changed(int new_cnt, int delta) {} + virtual void on_invoked(lua_State *state, int nargs, bool from_c) {} + }; + + DFHACK_EXPORT void New(lua_State *state, Owner *owner = NULL); + DFHACK_EXPORT void Make(lua_State *state, void *key, Owner *owner = NULL); + DFHACK_EXPORT void SetPrivateCallback(lua_State *state, int ev_idx); + DFHACK_EXPORT void Invoke(color_ostream &out, lua_State *state, void *key, int num_args); + } class StackUnwinder { lua_State *state; @@ -355,18 +374,24 @@ namespace DFHack {namespace Lua { } } - class DFHACK_EXPORT Notification { + class DFHACK_EXPORT Notification : public Event::Owner { lua_State *state; void *key; function_identity_base *handler; + int count; public: Notification(function_identity_base *handler = NULL) - : state(NULL), key(NULL), handler(handler) {} + : state(NULL), key(NULL), handler(handler), count(0) {} + int get_listener_count() { return count; } lua_State *get_state() { return state; } function_identity_base *get_handler() { return handler; } + lua_State *state_if_count() { return (count > 0) ? state : NULL; } + + void on_count_changed(int new_cnt, int) { count = new_cnt; } + void invoke(color_ostream &out, int nargs); void bind(lua_State *state, const char *name); @@ -378,7 +403,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out) { \ handler(out); \ - if (name##_event.get_state()) { \ + if (name##_event.state_if_count()) { \ name##_event.invoke(out, 0); \ } \ } @@ -387,7 +412,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1) { \ handler(out, arg1); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ name##_event.invoke(out, 1); \ } \ @@ -397,7 +422,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2) { \ handler(out, arg1, arg2); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ name##_event.invoke(out, 2); \ @@ -408,7 +433,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3) { \ handler(out, arg1, arg2, arg3); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ DFHack::Lua::Push(state, arg3); \ @@ -420,7 +445,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4) { \ handler(out, arg1, arg2, arg3, arg4); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ DFHack::Lua::Push(state, arg3); \ @@ -433,7 +458,7 @@ namespace DFHack {namespace Lua { static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4, arg_type5 arg5) { \ handler(out, arg1, arg2, arg3, arg4, arg5); \ - if (auto state = name##_event.get_state()) { \ + if (auto state = name##_event.state_if_count()) { \ DFHack::Lua::Push(state, arg1); \ DFHack::Lua::Push(state, arg2); \ DFHack::Lua::Push(state, arg3); \ diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index c51df3c6..0e5f618e 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -283,6 +283,9 @@ namespace DFHack /// modify permisions of memory range bool setPermisions(const t_memrange & range,const t_memrange &trgrange); + + /// write a possibly read-only memory area + bool patchMemory(void *target, const void* src, size_t count); private: VersionInfo * my_descriptor; PlatformSpecific *d; diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 5da9fc92..25b05ad4 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -173,31 +173,16 @@ namespace DFHack PluginManager * parent; plugin_state state; - struct LuaCommand { - Plugin *owner; - std::string name; - int (*command)(lua_State *state); - LuaCommand(Plugin *owner, std::string name) : owner(owner), name(name) {} - }; + struct LuaCommand; std::map<std::string, LuaCommand*> lua_commands; static int lua_cmd_wrapper(lua_State *state); - struct LuaFunction { - Plugin *owner; - std::string name; - function_identity_base *identity; - LuaFunction(Plugin *owner, std::string name) : owner(owner), name(name) {} - }; + struct LuaFunction; std::map<std::string, LuaFunction*> lua_functions; static int lua_fun_wrapper(lua_State *state); void push_function(lua_State *state, LuaFunction *fn); - struct LuaEvent { - LuaFunction handler; - Lua::Notification *event; - bool active; - LuaEvent(Plugin *owner, std::string name) : handler(owner,name), active(false) {} - }; + struct LuaEvent; std::map<std::string, LuaEvent*> lua_events; void index_lua(DFLibrary *lib); @@ -209,7 +194,7 @@ namespace DFHack command_result (*plugin_onupdate)(color_ostream &); command_result (*plugin_onstatechange)(color_ostream &, state_change_event); RPCService* (*plugin_rpcconnect)(color_ostream &); - command_result (*plugin_eval_ruby)(const char*); + command_result (*plugin_eval_ruby)(color_ostream &, const char*); }; class DFHACK_EXPORT PluginManager { @@ -238,7 +223,7 @@ namespace DFHack { return all_plugins.size(); } - command_result (*eval_ruby)(const char*); + command_result (*eval_ruby)(color_ostream &, const char*); // DATA private: tthread::mutex * cmdlist_mutex; diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h new file mode 100644 index 00000000..bb7a37ce --- /dev/null +++ b/library/include/VTableInterpose.h @@ -0,0 +1,173 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once + +#include "DataFuncs.h" + +namespace DFHack +{ + template<bool> struct StaticAssert; + template<> struct StaticAssert<true> {}; + +#define STATIC_ASSERT(condition) { StaticAssert<(condition)>(); } + + /* Wrapping around compiler-specific representation of pointers to methods. */ + +#if defined(_MSC_VER) +#define METHOD_POINTER_SIZE (sizeof(void*)*2) +#elif defined(__GXX_ABI_VERSION) +#define METHOD_POINTER_SIZE (sizeof(void*)*2) +#else +#error Unknown compiler type +#endif + +#define ASSERT_METHOD_POINTER(type) \ + STATIC_ASSERT(df::return_type<type>::is_method && sizeof(type)==METHOD_POINTER_SIZE); + + DFHACK_EXPORT bool is_vmethod_pointer_(void*); + DFHACK_EXPORT int vmethod_pointer_to_idx_(void*); + DFHACK_EXPORT void* method_pointer_to_addr_(void*); + DFHACK_EXPORT void addr_to_method_pointer_(void*,void*); + + template<class T> bool is_vmethod_pointer(T ptr) { + ASSERT_METHOD_POINTER(T); + return is_vmethod_pointer_(&ptr); + } + template<class T> int vmethod_pointer_to_idx(T ptr) { + ASSERT_METHOD_POINTER(T); + return vmethod_pointer_to_idx_(&ptr); + } + template<class T> void *method_pointer_to_addr(T ptr) { + ASSERT_METHOD_POINTER(T); + return method_pointer_to_addr_(&ptr); + } + template<class T> T addr_to_method_pointer(void *addr) { + ASSERT_METHOD_POINTER(T); + T rv; + addr_to_method_pointer_(&rv, addr); + return rv; + } + + /* Access to vmethod pointers from the vtable. */ + + template<class P> + P virtual_identity::get_vmethod_ptr(P selector) + { + typedef typename df::return_type<P>::class_type host_class; + virtual_identity &identity = host_class::_identity; + int idx = vmethod_pointer_to_idx(selector); + return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx)); + } + + /* VMethod interpose API. + + This API allows replacing an entry in the original vtable + with code defined by DFHack, while retaining ability to + call the original code. The API can be safely used from + plugins, and multiple hooks for the same vmethod are + automatically chained in undefined order. + + Usage: + + struct my_hack : df::someclass { + typedef df::someclass interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) { + // If needed by the code, claim the suspend lock. + // DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK! + // CoreSuspendClaimer suspend; + ... + INTERPOSE_NEXT(foo)(arg) // call the original + ... + } + }; + + IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); + + void init() { + if (!INTERPOSE_HOOK(my_hack, foo).apply()) + error(); + } + + void shutdown() { + INTERPOSE_HOOK(my_hack, foo).remove(); + } + */ + +#define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \ + typedef rtype (interpose_base::*interpose_ptr_##name)args; \ + static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \ + rtype interpose_fn_##name args + +#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \ + DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \ + class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name); + +#define INTERPOSE_NEXT(name) (this->*interpose_##name.chain) +#define INTERPOSE_HOOK(class, name) (class::interpose_##name) + + class DFHACK_EXPORT VMethodInterposeLinkBase { + /* + These link objects try to: + 1) Allow multiple hooks into the same vmethod + 2) Auto-remove hooks when a plugin is unloaded. + */ + + virtual_identity *host; // Class with the vtable + int vmethod_idx; + void *interpose_method; // Pointer to the code of the interposing method + void *chain_mptr; // Pointer to the chain field below + + void *saved_chain; // Previous pointer to the code + VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method + + void set_chain(void *chain); + public: + VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr); + ~VMethodInterposeLinkBase(); + + bool is_applied() { return saved_chain != NULL; } + bool apply(); + void remove(); + }; + + template<class Base, class Ptr> + class VMethodInterposeLink : public VMethodInterposeLinkBase { + public: + Ptr chain; + + operator Ptr () { return chain; } + + template<class Ptr2> + VMethodInterposeLink(Ptr target, Ptr2 src) + : VMethodInterposeLinkBase( + &Base::_identity, + vmethod_pointer_to_idx(target), + method_pointer_to_addr(src), + &chain + ) + { src = target; /* check compatibility */ } + }; +} diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 6e0a2205..639df686 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -93,6 +93,11 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building); DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes); /** + * Sets the owner unit for the building. + */ +DFHACK_EXPORT bool setOwner(df::building *building, df::unit *owner); + +/** * Find the building located at the specified tile. * Does not work on civzones. */ diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index e7155c43..273d84ce 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -55,8 +55,6 @@ namespace DFHack */ namespace Gui { - inline df::viewscreen *getCurViewscreen() { return Core::getTopViewscreen(); } - DFHACK_EXPORT std::string getFocusString(df::viewscreen *top); // Full-screen item details view @@ -113,7 +111,11 @@ namespace DFHack * Gui screens */ /// Get the current top-level view-screen - DFHACK_EXPORT df::viewscreen * GetCurrentScreen(); + DFHACK_EXPORT df::viewscreen *getCurViewscreen(bool skip_dismissed = false); + + inline std::string getCurFocus(bool skip_dismissed = false) { + return getFocusString(getCurViewscreen(skip_dismissed)); + } /// get the size of the window buffer DFHACK_EXPORT bool getWindowSize(int32_t & width, int32_t & height); diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h new file mode 100644 index 00000000..492e1eec --- /dev/null +++ b/library/include/modules/Screen.h @@ -0,0 +1,176 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once +#include "Export.h" +#include "Module.h" +#include "BitArray.h" +#include "ColorText.h" +#include <string> + +#include "DataDefs.h" +#include "df/graphic.h" +#include "df/viewscreen.h" + +/** + * \defgroup grp_screen utilities for painting to the screen + * @ingroup grp_screen + */ + +namespace DFHack +{ + class Core; + + /** + * The Screen module + * \ingroup grp_modules + * \ingroup grp_screen + */ + namespace Screen + { + /// Data structure describing all properties a screen tile can have + struct Pen { + // Ordinary text symbol + char ch; + int8_t fg, bg; + bool bold; + + // Graphics tile + int tile; + enum TileMode { + AsIs, // Tile colors used without modification + CharColor, // The fg/bg pair is used + TileColor // The fields below are used + } tile_mode; + int8_t tile_fg, tile_bg; + + Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false) + : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), + tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) + {} + Pen(char ch, int8_t fg, int8_t bg, bool bold, int tile = 0, bool color_tile = false) + : ch(ch), fg(fg), bg(bg), bold(bold), + tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) + {} + Pen(char ch, int8_t fg, int8_t bg, int tile, int8_t tile_fg, int8_t tile_bg) + : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), + tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg) + {} + Pen(char ch, int8_t fg, int8_t bg, bool bold, int tile, int8_t tile_fg, int8_t tile_bg) + : ch(ch), fg(fg), bg(bg), bold(bold), + tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg) + {} + }; + + DFHACK_EXPORT df::coord2d getMousePos(); + DFHACK_EXPORT df::coord2d getWindowSize(); + + /// Returns the state of [GRAPHICS:YES/NO] + DFHACK_EXPORT bool inGraphicsMode(); + + /// Paint one screen tile with the given pen + DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y); + + /// Paint a string onto the screen. Ignores ch and tile of pen. + DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text); + + /// Fills a rectangle with one pen. Possibly more efficient than a loop over paintTile. + DFHACK_EXPORT bool fillRect(const Pen &pen, int x1, int y1, int x2, int y2); + + /// Draws a standard dark gray window border with a title string + DFHACK_EXPORT bool drawBorder(const std::string &title); + + /// Wipes the screen to full black + DFHACK_EXPORT bool clear(); + + /// Requests repaint + DFHACK_EXPORT bool invalidate(); + + /// Find a loaded graphics tile from graphics raws. + DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL); + + // Push and remove viewscreens + DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); + DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); + DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); + } + + class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { + df::coord2d last_size; + void check_resize(); + + protected: + bool text_input_mode; + + public: + dfhack_viewscreen(); + virtual ~dfhack_viewscreen(); + + static bool is_instance(df::viewscreen *screen); + + virtual void logic(); + virtual void render(); + + virtual int8_t movies_okay() { return 1; } + virtual bool key_conflict(df::interface_key key); + + virtual bool is_lua_screen() { return false; } + + virtual std::string getFocusString() = 0; + virtual void onShow() {}; + virtual void onDismiss() {}; + }; + + class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { + std::string focus; + + void update_focus(lua_State *L, int idx); + + bool safe_call_lua(int (*pf)(lua_State *), int args, int rvs); + static dfhack_lua_viewscreen *get_self(lua_State *L); + + static int do_destroy(lua_State *L); + static int do_render(lua_State *L); + static int do_notify(lua_State *L); + static int do_input(lua_State *L); + + public: + dfhack_lua_viewscreen(lua_State *L, int table_idx); + virtual ~dfhack_lua_viewscreen(); + + static df::viewscreen *get_pointer(lua_State *L, int idx, bool make); + + virtual bool is_lua_screen() { return true; } + virtual std::string getFocusString() { return focus; } + + virtual void render(); + virtual void logic(); + virtual void help(); + virtual void resize(int w, int h); + virtual void feed(std::set<df::interface_key> *keys); + + virtual void onShow(); + virtual void onDismiss(); + }; +} diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 7bf4f101..9003dc3a 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -226,6 +226,9 @@ DFHACK_EXPORT bool getNoblePositions(std::vector<NoblePosition> *pvec, df::unit DFHACK_EXPORT std::string getProfessionName(df::unit *unit, bool ignore_noble = false, bool plural = false); DFHACK_EXPORT std::string getCasteProfessionName(int race, int caste, df::profession pid, bool plural = false); + +DFHACK_EXPORT int8_t getProfessionColor(df::unit *unit, bool ignore_noble = false); +DFHACK_EXPORT int8_t getCasteProfessionColor(int race, int caste, df::profession pid); } } #endif diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index d56d4df6..2cbd019a 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -39,6 +39,8 @@ if dfhack.is_core_context then SC_MAP_UNLOADED = 3 SC_VIEWSCREEN_CHANGED = 4 SC_CORE_INITIALIZED = 5 + SC_PAUSED = 7 + SC_UNPAUSED = 8 end -- Error handling @@ -100,11 +102,39 @@ function reload(module) dofile(path) end +-- Trivial classes + +function rawset_default(target,source) + for k,v in pairs(source) do + if rawget(target,k) == nil then + rawset(target,k,v) + end + end +end + +function defclass(class,parent) + class = class or {} + rawset_default(class, { __index = class }) + if parent then + setmetatable(class, parent) + else + rawset_default(class, { init_fields = rawset_default }) + end + return class +end + +function mkinstance(class,table) + table = table or {} + setmetatable(table, class) + return table +end + -- Misc functions function printall(table) - if type(table) == 'table' or df.isvalid(table) == 'ref' then - for k,v in pairs(table) do + local ok,f,t,k = pcall(pairs,table) + if ok then + for k,v in f,t,k do print(string.format("%-23s\t = %s",tostring(k),tostring(v))) end end @@ -133,14 +163,6 @@ function xyz2pos(x,y,z) end end -function rawset_default(target,source) - for k,v in pairs(source) do - if rawget(target,k) == nil then - rawset(target,k,v) - end - end -end - function safe_index(obj,idx,...) if obj == nil or idx == nil then return nil @@ -158,10 +180,6 @@ end -- String conversions -function dfhack.event:__tostring() - return "<event>" -end - function dfhack.persistent:__tostring() return "<persistent "..self.entry_id..":"..self.key.."=\"" ..self.value.."\":"..table.concat(self.ints,",")..">" diff --git a/library/lua/gui.lua b/library/lua/gui.lua new file mode 100644 index 00000000..ac032166 --- /dev/null +++ b/library/lua/gui.lua @@ -0,0 +1,400 @@ +-- Viewscreen implementation utility collection. + +local _ENV = mkmodule('gui') + +local dscreen = dfhack.screen + +USE_GRAPHICS = dscreen.inGraphicsMode() + +CLEAR_PEN = {ch=32,fg=0,bg=0} + +function simulateInput(screen,...) + local keys = {} + local function push_key(arg) + local kv = arg + if type(arg) == 'string' then + kv = df.interface_key[arg] + if kv == nil then + error('Invalid keycode: '..arg) + end + end + if type(arg) == 'number' then + keys[#keys+1] = kv + end + end + for i = 1,select('#',...) do + local arg = select(i,...) + if arg ~= nil then + local t = type(arg) + if type(arg) == 'table' then + for k,v in pairs(arg) do + if v == true then + push_key(k) + else + push_key(v) + end + end + else + push_key(arg) + end + end + end + dscreen._doSimulateInput(screen, keys) +end + +function mkdims_xy(x1,y1,x2,y2) + return { x1=x1, y1=y1, x2=x2, y2=y2, width=x2-x1+1, height=y2-y1+1 } +end +function mkdims_wh(x1,y1,w,h) + return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h } +end +function inset(rect,dx1,dy1,dx2,dy2) + return mkdims_xy( + rect.x1+dx1, rect.y1+dy1, + rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1) + ) +end +function is_in_rect(rect,x,y) + return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2 +end + +local function to_pen(default, pen, bg, bold) + if pen == nil then + return default or {} + elseif type(pen) ~= 'table' then + return {fg=pen,bg=bg,bold=bold} + else + return pen + end +end + +---------------------------- +-- Clipped painter object -- +---------------------------- + +Painter = defclass(Painter, nil) + +function Painter.new(rect, pen) + rect = rect or mkdims_wh(0,0,dscreen.getWindowSize()) + local self = { + x1 = rect.x1, clip_x1 = rect.x1, + y1 = rect.y1, clip_y1 = rect.y1, + x2 = rect.x2, clip_x2 = rect.x2, + y2 = rect.y2, clip_y2 = rect.y2, + width = rect.x2-rect.x1+1, + height = rect.y2-rect.y1+1, + cur_pen = to_pen(nil, pen or COLOR_GREY) + } + return mkinstance(Painter, self):seek(0,0) +end + +function Painter:isValidPos() + return self.x >= self.clip_x1 and self.x <= self.clip_x2 + and self.y >= self.clip_y1 and self.y <= self.clip_y2 +end + +function Painter:viewport(x,y,w,h) + local x1,y1 = self.x1+x, self.y1+y + local x2,y2 = x1+w-1, y1+h-1 + local vp = { + -- Logical viewport + x1 = x1, y1 = y1, x2 = x2, y2 = y2, + width = w, height = h, + -- Actual clipping rect + clip_x1 = math.max(self.clip_x1, x1), + clip_y1 = math.max(self.clip_y1, y1), + clip_x2 = math.min(self.clip_x2, x2), + clip_y2 = math.min(self.clip_y2, y2), + -- Pen + cur_pen = self.cur_pen + } + return mkinstance(Painter, vp):seek(0,0) +end + +function Painter:localX() + return self.x - self.x1 +end + +function Painter:localY() + return self.y - self.y1 +end + +function Painter:seek(x,y) + if x then self.x = self.x1 + x end + if y then self.y = self.y1 + y end + return self +end + +function Painter:advance(dx,dy) + if dx then self.x = self.x + dx end + if dy then self.y = self.y + dy end + return self +end + +function Painter:newline(dx) + self.y = self.y + 1 + self.x = self.x1 + (dx or 0) + return self +end + +function Painter:pen(pen,...) + self.cur_pen = to_pen(self.cur_pen, pen, ...) + return self +end + +function Painter:color(fg,bold,bg) + self.cur_pen = copyall(self.cur_pen) + self.cur_pen.fg = fg + self.cur_pen.bold = bold + if bg then self.cur_pen.bg = bg end + return self +end + +function Painter:clear() + dscreen.fillRect(CLEAR_PEN, self.clip_x1, self.clip_y1, self.clip_x2, self.clip_y2) + return self +end + +function Painter:fill(x1,y1,x2,y2,pen,bg,bold) + if type(x1) == 'table' then + x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2 + end + x1 = math.max(x1,self.clip_x1) + y1 = math.max(y1,self.clip_y1) + x2 = math.min(x2,self.clip_x2) + y2 = math.min(y2,self.clip_y2) + dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2) + return self +end + +function Painter:char(char,pen,...) + if self:isValidPos() then + dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char) + end + return self:advance(1, nil) +end + +function Painter:tile(char,tile,pen,...) + if self:isValidPos() then + dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile) + end + return self:advance(1, nil) +end + +function Painter:string(text,pen,...) + if self.y >= self.clip_y1 and self.y <= self.clip_y2 then + local dx = 0 + if self.x < self.clip_x1 then + dx = self.clip_x1 - self.x + end + local len = #text + if self.x + len - 1 > self.clip_x2 then + len = self.clip_x2 - self.x + 1 + end + if len > dx then + dscreen.paintString( + to_pen(self.cur_pen, pen, ...), + self.x+dx, self.y, + string.sub(text,dx+1,len) + ) + end + end + return self:advance(#text, nil) +end + +------------------------ +-- Base screen object -- +------------------------ + +Screen = defclass(Screen) + +Screen.text_input_mode = false + +function Screen:init() + self:updateLayout() + return self +end + +Screen.isDismissed = dscreen.isDismissed + +function Screen:isShown() + return self._native ~= nil +end + +function Screen:isActive() + return self:isShown() and not self:isDismissed() +end + +function Screen:invalidate() + dscreen.invalidate() +end + +function Screen:getWindowSize() + return dscreen.getWindowSize() +end + +function Screen:getMousePos() + return dscreen.getMousePos() +end + +function Screen:renderParent() + if self._native and self._native.parent then + self._native.parent:render() + else + dscreen.clear() + end +end + +function Screen:sendInputToParent(...) + if self._native and self._native.parent then + simulateInput(self._native.parent, ...) + end +end + +function Screen:show(below) + if self._native then + error("This screen is already on display") + end + self:onAboutToShow(below) + if not dscreen.show(self, below) then + error('Could not show screen') + end +end + +function Screen:onAboutToShow() +end + +function Screen:onShow() + self:updateLayout() +end + +function Screen:dismiss() + if self._native then + dscreen.dismiss(self) + end +end + +function Screen:onDismiss() +end + +function Screen:onResize(w,h) + self:updateLayout() +end + +function Screen:updateLayout() +end + +------------------------ +-- Framed screen object -- +------------------------ + +-- Plain grey-colored frame. +GREY_FRAME = { + frame_pen = { ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, + title_pen = { fg = COLOR_BLACK, bg = COLOR_WHITE }, + signature_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, +} + +-- The usual boundary used by the DF screens. Often has fancy pattern in tilesets. +BOUNDARY_FRAME = { + frame_pen = { ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK }, + title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, + signature_pen = { fg = COLOR_BLACK, bg = COLOR_DARKGREY }, +} + +GREY_LINE_FRAME = { + frame_pen = { ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK }, + h_frame_pen = { ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK }, + v_frame_pen = { ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK }, + lt_frame_pen = { ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK }, + lb_frame_pen = { ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK }, + rt_frame_pen = { ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK }, + rb_frame_pen = { ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK }, + title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, + signature_pen = { fg = COLOR_DARKGREY, bg = COLOR_BLACK }, +} + +function paint_frame(x1,y1,x2,y2,style,title) + local pen = style.frame_pen + dscreen.paintTile(style.lt_frame_pen or pen, x1, y1) + dscreen.paintTile(style.rt_frame_pen or pen, x2, y1) + dscreen.paintTile(style.lb_frame_pen or pen, x1, y2) + dscreen.paintTile(style.rb_frame_pen or pen, x2, y2) + dscreen.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1) + dscreen.fillRect(style.b_frame_pen or style.h_frame_pen or pen,x1+1,y2,x2-1,y2) + dscreen.fillRect(style.l_frame_pen or style.v_frame_pen or pen,x1,y1+1,x1,y2-1) + dscreen.fillRect(style.r_frame_pen or style.v_frame_pen or pen,x2,y1+1,x2,y2-1) + dscreen.paintString(style.signature_pen or style.title_pen or pen,x2-7,y2,"DFHack") + + if title then + local x = math.max(0,math.floor((x2-x1-3-#title)/2)) + x1 + local tstr = ' '..title..' ' + if #tstr > x2-x1-1 then + tstr = string.sub(tstr,1,x2-x1-1) + end + dscreen.paintString(style.title_pen or pen, x, y1, tstr) + end +end + +FramedScreen = defclass(FramedScreen, Screen) + +FramedScreen.frame_style = BOUNDARY_FRAME + +local function hint_coord(gap,hint) + if hint and hint > 0 then + return math.min(hint,gap) + elseif hint and hint < 0 then + return math.max(0,gap-hint) + else + return math.floor(gap/2) + end +end + +function FramedScreen:updateFrameSize() + local sw, sh = dscreen.getWindowSize() + local iw, ih = sw-2, sh-2 + local width = math.min(self.frame_width or iw, iw) + local height = math.min(self.frame_height or ih, ih) + local gw, gh = iw-width, ih-height + local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) + self.frame_rect = mkdims_wh(x1+1,y1+1,width,height) + self.frame_opaque = (gw == 0 and gh == 0) +end + +function FramedScreen:updateLayout() + self:updateFrameSize() +end + +function FramedScreen:getWindowSize() + local rect = self.frame_rect + return rect.width, rect.height +end + +function FramedScreen:getMousePos() + local rect = self.frame_rect + local x,y = dscreen.getMousePos() + if is_in_rect(rect,x,y) then + return x-rect.x1, y-rect.y1 + end +end + +function FramedScreen:onRender() + local rect = self.frame_rect + local x1,y1,x2,y2 = rect.x1-1, rect.y1-1, rect.x2+1, rect.y2+1 + + if self.frame_opaque then + dscreen.clear() + else + self:renderParent() + dscreen.fillRect(CLEAR_PEN,x1,y1,x2,y2) + end + + paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) + + self:onRenderBody(Painter.new(rect)) +end + +function FramedScreen:onRenderBody(dc) +end + +return _ENV diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua new file mode 100644 index 00000000..c1a8bcb9 --- /dev/null +++ b/library/lua/gui/dwarfmode.lua @@ -0,0 +1,279 @@ +-- Support for messing with the dwarfmode screen + +local _ENV = mkmodule('gui.dwarfmode') + +local gui = require('gui') +local utils = require('utils') + +local dscreen = dfhack.screen +local world_map = df.global.world.map + +AREA_MAP_WIDTH = 23 +MENU_WIDTH = 30 + +function getPanelLayout() + local sw, sh = dscreen.getWindowSize() + local view_height = sh-2 + local view_rb = sw-1 + local area_x2 = sw-AREA_MAP_WIDTH-2 + local menu_x2 = sw-MENU_WIDTH-2 + local menu_x1 = area_x2-MENU_WIDTH-1 + local area_pos = df.global.ui_area_map_width + local menu_pos = df.global.ui_menu_width + local rv = {} + + if area_pos < 3 then + rv.area_map = gui.mkdims_xy(area_x2+1,1,view_rb-1,view_height) + view_rb = area_x2 + end + if menu_pos < area_pos or df.global.ui.main.mode ~= 0 then + if menu_pos >= area_pos then + rv.menu_forced = true + menu_pos = area_pos-1 + end + local menu_x = menu_x2 + if menu_pos < 2 then menu_x = menu_x1 end + rv.menu = gui.mkdims_xy(menu_x+1,1,view_rb-1,view_height) + view_rb = menu_x + end + rv.area_pos = area_pos + rv.menu_pos = menu_pos + rv.map = gui.mkdims_xy(1,1,view_rb-1,view_height) + return rv +end + +function getCursorPos() + if df.global.cursor.x ~= -30000 then + return copyall(df.global.cursor) + end +end + +function setCursorPos(cursor) + df.global.cursor = cursor +end + +function clearCursorPos() + df.global.cursor = xyz2pos(nil) +end + +Viewport = defclass(Viewport) + +function Viewport.make(map,x,y,z) + local self = gui.mkdims_wh(x,y,map.width,map.height) + self.z = z + return mkinstance(Viewport, self) +end + +function Viewport.get(layout) + return Viewport.make( + (layout or getPanelLayout()).map, + df.global.window_x, + df.global.window_y, + df.global.window_z + ) +end + +function Viewport:resize(layout) + return Viewport.make( + (layout or getPanelLayout()).map, + self.x1, self.y1, self.z + ) +end + +function Viewport:set() + local vp = self:clip() + df.global.window_x = vp.x1 + df.global.window_y = vp.y1 + df.global.window_z = vp.z + return vp +end + +function Viewport:clip(x,y,z) + return self:make( + math.max(0, math.min(x or self.x1, world_map.x_count-self.width)), + math.max(0, math.min(y or self.y1, world_map.y_count-self.height)), + math.max(0, math.min(z or self.z, world_map.z_count-1)) + ) +end + +function Viewport:isVisibleXY(target,gap) + gap = gap or 0 + + return math.max(target.x-gap,0) >= self.x1 + and math.min(target.x+gap,world_map.x_count-1) <= self.x2 + and math.max(target.y-gap,0) >= self.y1 + and math.min(target.y+gap,world_map.y_count-1) <= self.y2 +end + +function Viewport:isVisible(target,gap) + gap = gap or 0 + + return self:isVisibleXY(target,gap) and target.z == self.z +end + +function Viewport:centerOn(target) + return self:clip( + target.x - math.floor(self.width/2), + target.y - math.floor(self.height/2), + target.z + ) +end + +function Viewport:scrollTo(target,gap) + gap = math.max(0, gap or 5) + if gap*2 >= math.min(self.width, self.height) then + gap = math.floor(math.min(self.width, self.height)/2) + end + local x = math.min(self.x1, target.x-gap) + x = math.max(x, target.x+gap+1-self.width) + local y = math.min(self.y1, target.y-gap) + y = math.max(y, target.y+gap+1-self.height) + return self:clip(x, y, target.z) +end + +function Viewport:reveal(target,gap,max_scroll,scroll_gap,scroll_z) + gap = math.max(0, gap or 5) + if self:isVisible(target, gap) then + return self + end + + max_scroll = math.max(0, max_scroll or 5) + if self:isVisibleXY(target, -max_scroll) + and (scroll_z or target.z == self.z) then + return self:scrollTo(target, scroll_gap or gap) + else + return self:centerOn(target) + end +end + +MOVEMENT_KEYS = { + CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 }, + CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 }, + CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 }, + CURSOR_DOWNLEFT = { -1, 1, 0 }, CURSOR_DOWNRIGHT = { 1, 1, 0 }, + CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true }, + CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true }, + CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true }, + CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true }, + CURSOR_UP_Z = { 0, 0, 1 }, CURSOR_DOWN_Z = { 0, 0, -1 }, + CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, +} + +function Viewport:scrollByKey(key) + local info = MOVEMENT_KEYS[key] + if info then + local delta = 10 + if info[4] then delta = 20 end + + return self:clip( + self.x1 + delta*info[1], + self.y1 + delta*info[2], + self.z + info[3] + ) + else + return self + end +end + +DwarfOverlay = defclass(DwarfOverlay, gui.Screen) + +function DwarfOverlay:updateLayout() + self.df_layout = getPanelLayout() +end + +function DwarfOverlay:getViewport(old_vp) + if old_vp then + return old_vp:resize(self.df_layout) + else + return Viewport.get(self.df_layout) + end +end + +function DwarfOverlay:moveCursorTo(cursor,viewport) + setCursorPos(cursor) + self:getViewport(viewport):reveal(cursor, 5, 0, 10):set() +end + +function DwarfOverlay:selectBuilding(building,cursor,viewport) + cursor = cursor or utils.getBuildingCenter(building) + + df.global.world.selected_building = building + self:moveCursorTo(cursor, viewport) +end + +function DwarfOverlay:propagateMoveKeys(keys) + for code,_ in pairs(MOVEMENT_KEYS) do + if keys[code] then + self:sendInputToParent(code) + return code + end + end +end + +function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor) + local layout = self.df_layout + local cursor = getCursorPos() + + anchor = anchor or cursor + + if anchor and keys.A_MOVE_SAME_SQUARE then + self:getViewport():centerOn(anchor):set() + return 'A_MOVE_SAME_SQUARE' + end + + for code,_ in pairs(MOVEMENT_KEYS) do + if keys[code] then + local vp = self:getViewport():scrollByKey(code) + if (cursor and not no_clip_cursor) or no_clip_cursor == false then + vp = vp:reveal(anchor,4,20,4,true) + end + vp:set() + + return code + end + end +end + +function DwarfOverlay:onAboutToShow(below) + local screen = dfhack.gui.getCurViewscreen() + if below then screen = below.parent end + if not df.viewscreen_dwarfmodest:is_instance(screen) then + error("This screen requires the main dwarfmode view") + end +end + +MenuOverlay = defclass(MenuOverlay, DwarfOverlay) + +function MenuOverlay:updateLayout() + DwarfOverlay.updateLayout(self) + self.frame_rect = self.df_layout.menu +end + +MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize +MenuOverlay.getMousePos = gui.FramedScreen.getMousePos + +function MenuOverlay:onAboutToShow(below) + DwarfOverlay.onAboutToShow(self,below) + + self:updateLayout() + if not self.df_layout.menu then + error("The menu panel of dwarfmode is not visible") + end +end + +function MenuOverlay:onRender() + self:renderParent() + + local menu = self.df_layout.menu + if menu then + -- Paint signature on the frame. + dscreen.paintString( + {fg=COLOR_BLACK,bg=COLOR_DARKGREY}, + menu.x1+1, menu.y2+1, "DFHack" + ) + + self:onRenderBody(gui.Painter.new(menu)) + end +end + +return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index f303091d..19a4e6f6 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -57,10 +57,10 @@ function is_container(obj) end -- Make a sequence of numbers in 1..size -function make_index_sequence(size) +function make_index_sequence(istart,iend) local index = {} - for i=1,size do - index[i] = i + for i=istart,iend do + index[i-istart+1] = i end return index end @@ -114,7 +114,7 @@ function make_sort_order(data,ordering) end -- Make an order table - local index = make_index_sequence(size) + local index = make_index_sequence(1,size) -- Sort the ordering table table.sort(index, function(ia,ib) @@ -361,6 +361,26 @@ function insert_or_update(vector,item,field,cmp) return added,cur,pos end +-- Calls a method with a string temporary +function call_with_string(obj,methodname,...) + return dfhack.with_temp_object( + df.new "string", + function(str,obj,methodname,...) + obj[methodname](obj,str,...) + return str.value + end, + obj,methodname,... + ) +end + +function getBuildingName(building) + return call_with_string(building, 'getName') +end + +function getBuildingCenter(building) + return xyz2pos(building.centerx, building.centery, building.z) +end + -- Ask a yes-no question function prompt_yes_no(msg,default) local prompt = msg @@ -379,7 +399,7 @@ function prompt_yes_no(msg,default) elseif string.match(rv,'^[Nn]') then return false elseif rv == 'abort' then - qerror('User abort in utils.prompt_yes_no()') + qerror('User abort') elseif rv == '' and default ~= nil then return default end @@ -393,7 +413,7 @@ function prompt_input(prompt,check,quit_str) while true do local rv = dfhack.lineedit(prompt) if rv == quit_str then - return nil + qerror('User abort') end local rtbl = table.pack(check(rv)) if rtbl[1] then diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index e0afc56e..d1aed897 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -49,6 +49,7 @@ using namespace DFHack; #include "df/ui_look_list.h" #include "df/d_init.h" #include "df/item.h" +#include "df/unit.h" #include "df/job.h" #include "df/job_item.h" #include "df/general_ref_building_holderst.h" @@ -177,6 +178,44 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes) return true; } +bool Buildings::setOwner(df::building *bld, df::unit *unit) +{ + CHECK_NULL_POINTER(bld); + + if (!bld->is_room) + return false; + if (bld->owner == unit) + return true; + + if (bld->owner) + { + auto &blist = bld->owner->owned_buildings; + vector_erase_at(blist, linear_index(blist, bld)); + + if (auto spouse = df::unit::find(bld->owner->relations.spouse_id)) + { + auto &blist = spouse->owned_buildings; + vector_erase_at(blist, linear_index(blist, bld)); + } + } + + bld->owner = unit; + + if (unit) + { + unit->owned_buildings.push_back(bld); + + if (auto spouse = df::unit::find(unit->relations.spouse_id)) + { + auto &blist = spouse->owned_buildings; + if (bld->canUseSpouseRoom() && linear_index(blist, bld) < 0) + blist.push_back(bld); + } + } + + return true; +} + df::building *Buildings::findAtTile(df::coord pos) { auto occ = Maps::getTileOccupancy(pos); @@ -991,7 +1030,7 @@ bool Buildings::deconstruct(df::building *bld) // Assume: no parties. unlinkRooms(bld); // Assume: not unit destroy target - vector_erase_at(ui->unk8.unk10, linear_index(ui->unk8.unk10, bld)); + vector_erase_at(ui->tax_collection.rooms, linear_index(ui->tax_collection.rooms, bld)); // Assume: not used in punishment // Assume: not used in non-own jobs // Assume: does not affect pathfinding diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index cd44401f..91a17e99 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -42,6 +42,7 @@ using namespace std; using namespace DFHack; #include "modules/Job.h" +#include "modules/Screen.h" #include "DataDefs.h" #include "df/world.h" @@ -466,6 +467,11 @@ std::string Gui::getFocusString(df::viewscreen *top) return name; } + else if (dfhack_viewscreen::is_instance(top)) + { + auto name = static_cast<dfhack_viewscreen*>(top)->getFocusString(); + return name.empty() ? "dfhack" : "dfhack/"+name; + } else { Core &core = Core::getInstance(); @@ -977,17 +983,19 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright) world->status.popups.push_back(popup); } -df::viewscreen * Gui::GetCurrentScreen() +df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) { df::viewscreen * ws = &gview->view; - while(ws) + while (ws && ws->child) + ws = ws->child; + + if (skip_dismissed) { - if(ws->child) - ws = ws->child; - else - return ws; + while (ws && Screen::isDismissed(ws) && ws->parent) + ws = ws->parent; } - return 0; + + return ws; } bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index a5e73bf1..b74a4b73 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -189,7 +189,7 @@ void DFHack::Job::printJobDetails(color_ostream &out, df::job *job) { CHECK_NULL_POINTER(job); - out.color(job->flags.bits.suspend ? Console::COLOR_DARKGREY : Console::COLOR_GREY); + out.color(job->flags.bits.suspend ? COLOR_DARKGREY : COLOR_GREY); out << "Job " << job->id << ": " << ENUM_KEY_STR(job_type,job->job_type); if (job->flags.whole) out << " (" << bitfield_to_string(job->flags) << ")"; diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index f8f99f81..50cf21a9 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -293,6 +293,12 @@ std::string MaterialInfo::getToken() switch (mode) { case Builtin: + if (material->id == "COAL") { + if (index == 0) + return "COAL:COKE"; + else if (index == 1) + return "COAL:CHARCOAL"; + } return material->id; case Inorganic: return "INORGANIC:" + inorganic->id; diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp new file mode 100644 index 00000000..c2377f2c --- /dev/null +++ b/library/modules/Screen.cpp @@ -0,0 +1,604 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#include "Internal.h" + +#include <string> +#include <vector> +#include <map> +using namespace std; + +#include "modules/Screen.h" +#include "MemAccess.h" +#include "VersionInfo.h" +#include "Types.h" +#include "Error.h" +#include "ModuleFactory.h" +#include "Core.h" +#include "PluginManager.h" +#include "LuaTools.h" + +#include "MiscUtils.h" + +using namespace DFHack; + +#include "DataDefs.h" +#include "df/init.h" +#include "df/texture_handler.h" +#include "df/tile_page.h" +#include "df/interfacest.h" +#include "df/enabler.h" + +using namespace df::enums; +using df::global::init; +using df::global::gps; +using df::global::texture; +using df::global::gview; +using df::global::enabler; + +using Screen::Pen; + +/* + * Screen painting API. + */ + +df::coord2d Screen::getMousePos() +{ + if (!gps || (enabler && !enabler->tracking_on)) + return df::coord2d(-1, -1); + + return df::coord2d(gps->mouse_x, gps->mouse_y); +} + +df::coord2d Screen::getWindowSize() +{ + if (!gps) return df::coord2d(80, 25); + + return df::coord2d(gps->dimx, gps->dimy); +} + +bool Screen::inGraphicsMode() +{ + return init && init->display.flag.is_set(init_display_flags::USE_GRAPHICS); +} + +static void doSetTile(const Pen &pen, int index) +{ + auto screen = gps->screen + index*4; + screen[0] = uint8_t(pen.ch); + screen[1] = uint8_t(pen.fg) & 15; + screen[2] = uint8_t(pen.bg) & 15; + screen[3] = uint8_t(pen.bold) & 1; + gps->screentexpos[index] = pen.tile; + gps->screentexpos_addcolor[index] = (pen.tile_mode == Screen::Pen::CharColor); + gps->screentexpos_grayscale[index] = (pen.tile_mode == Screen::Pen::TileColor); + gps->screentexpos_cf[index] = pen.tile_fg; + gps->screentexpos_cbr[index] = pen.tile_bg; +} + +bool Screen::paintTile(const Pen &pen, int x, int y) +{ + if (!gps) return false; + + int dimx = gps->dimx, dimy = gps->dimy; + if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; + + doSetTile(pen, x*dimy + y); + return true; +} + +bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) +{ + if (!gps || y < 0 || y >= gps->dimy) return false; + + Pen tmp(pen); + bool ok = false; + + for (size_t i = -std::min(0,x); i < text.size(); i++) + { + if (x + i >= size_t(gps->dimx)) + break; + + tmp.ch = text[i]; + tmp.tile = (pen.tile ? pen.tile + uint8_t(text[i]) : 0); + paintTile(tmp, x+i, y); + ok = true; + } + + return ok; +} + +bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) +{ + if (!gps) return false; + + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (x2 >= gps->dimx) x2 = gps->dimx-1; + if (y2 >= gps->dimy) y2 = gps->dimy-1; + if (x1 > x2 || y1 > y2) return false; + + for (int x = x1; x <= x2; x++) + { + int index = x*gps->dimy; + + for (int y = y1; y <= y2; y++) + doSetTile(pen, index+y); + } + + return true; +} + +bool Screen::drawBorder(const std::string &title) +{ + if (!gps) return false; + + int dimx = gps->dimx, dimy = gps->dimy; + Pen border(0xDB, 8); + Pen text(0, 0, 7); + Pen signature(0, 0, 8); + + for (int x = 0; x < dimx; x++) + { + doSetTile(border, x * dimy + 0); + doSetTile(border, x * dimy + dimy - 1); + } + for (int y = 0; y < dimy; y++) + { + doSetTile(border, 0 * dimy + y); + doSetTile(border, (dimx - 1) * dimy + y); + } + + paintString(signature, dimx-8, dimy-1, "DFHack"); + + return paintString(text, (dimx - title.length()) / 2, 0, title); +} + +bool Screen::clear() +{ + if (!gps) return false; + + return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1); +} + +bool Screen::invalidate() +{ + if (!enabler) return false; + + enabler->flag.bits.render = true; + return true; +} + +bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs) +{ + if (!gps || !texture || x < 0 || y < 0) return false; + + for (size_t i = 0; i < texture->page.size(); i++) + { + auto page = texture->page[i]; + if (!page->loaded || page->token != pagename) continue; + + if (x >= page->page_dim_x || y >= page->page_dim_y) + break; + int idx = y*page->page_dim_x + x; + if (size_t(idx) >= page->texpos.size()) + break; + + if (ptile) *ptile = page->texpos[idx]; + if (pgs) *pgs = page->texpos_gs[idx]; + return true; + } + + return false; +} + +bool Screen::show(df::viewscreen *screen, df::viewscreen *before) +{ + CHECK_NULL_POINTER(screen); + CHECK_INVALID_ARGUMENT(!screen->parent && !screen->child); + + if (!gps || !gview) return false; + + df::viewscreen *parent = &gview->view; + while (parent && parent->child != before) + parent = parent->child; + + if (!parent) return false; + + gps->force_full_display_count += 2; + + screen->child = parent->child; + screen->parent = parent; + parent->child = screen; + if (screen->child) + screen->child->parent = screen; + + if (dfhack_viewscreen::is_instance(screen)) + static_cast<dfhack_viewscreen*>(screen)->onShow(); + + return true; +} + +void Screen::dismiss(df::viewscreen *screen, bool to_first) +{ + CHECK_NULL_POINTER(screen); + + if (screen->breakdown_level != interface_breakdown_types::NONE) + return; + + if (to_first) + screen->breakdown_level = interface_breakdown_types::TOFIRST; + else + screen->breakdown_level = interface_breakdown_types::STOPSCREEN; + + if (dfhack_viewscreen::is_instance(screen)) + static_cast<dfhack_viewscreen*>(screen)->onDismiss(); +} + +bool Screen::isDismissed(df::viewscreen *screen) +{ + CHECK_NULL_POINTER(screen); + + return screen->breakdown_level != interface_breakdown_types::NONE; +} + +/* + * Base DFHack viewscreen. + */ + +static std::set<df::viewscreen*> dfhack_screens; + +dfhack_viewscreen::dfhack_viewscreen() : text_input_mode(false) +{ + dfhack_screens.insert(this); + + last_size = Screen::getWindowSize(); +} + +dfhack_viewscreen::~dfhack_viewscreen() +{ + dfhack_screens.erase(this); +} + +bool dfhack_viewscreen::is_instance(df::viewscreen *screen) +{ + return dfhack_screens.count(screen) != 0; +} + +void dfhack_viewscreen::check_resize() +{ + auto size = Screen::getWindowSize(); + + if (size != last_size) + { + last_size = size; + resize(size.x, size.y); + } +} + +void dfhack_viewscreen::logic() +{ + check_resize(); + + // Various stuff works poorly unless always repainting + Screen::invalidate(); +} + +void dfhack_viewscreen::render() +{ + check_resize(); +} + +bool dfhack_viewscreen::key_conflict(df::interface_key key) +{ + if (key == interface_key::OPTIONS) + return true; + + if (text_input_mode) + { + if (key == interface_key::HELP || key == interface_key::MOVIES) + return true; + } + + return false; +} + +/* + * Lua-backed viewscreen. + */ + +static int DFHACK_LUA_VS_TOKEN = 0; + +df::viewscreen *dfhack_lua_viewscreen::get_pointer(lua_State *L, int idx, bool make) +{ + df::viewscreen *screen; + + if (lua_istable(L, idx)) + { + if (!Lua::IsCoreContext(L)) + luaL_error(L, "only the core context can create lua screens"); + + lua_rawgetp(L, idx, &DFHACK_LUA_VS_TOKEN); + + if (!lua_isnil(L, -1)) + { + if (make) + luaL_error(L, "this screen is already on display"); + + screen = (df::viewscreen*)lua_touserdata(L, -1); + } + else + { + if (!make) + luaL_error(L, "this screen is not on display"); + + screen = new dfhack_lua_viewscreen(L, idx); + } + + lua_pop(L, 1); + } + else + screen = Lua::CheckDFObject<df::viewscreen>(L, idx); + + return screen; +} + +bool dfhack_lua_viewscreen::safe_call_lua(int (*pf)(lua_State *), int args, int rvs) +{ + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + + auto L = Lua::Core::State; + lua_pushcfunction(L, pf); + if (args > 0) lua_insert(L, -args-1); + lua_pushlightuserdata(L, this); + if (args > 0) lua_insert(L, -args-1); + + return Lua::Core::SafeCall(out, args+1, rvs); +} + +dfhack_lua_viewscreen *dfhack_lua_viewscreen::get_self(lua_State *L) +{ + auto self = (dfhack_lua_viewscreen*)lua_touserdata(L, 1); + lua_rawgetp(L, LUA_REGISTRYINDEX, self); + if (!lua_istable(L, -1)) return NULL; + return self; +} + +int dfhack_lua_viewscreen::do_destroy(lua_State *L) +{ + auto self = get_self(L); + if (!self) return 0; + + lua_pushnil(L); + lua_rawsetp(L, LUA_REGISTRYINDEX, self); + + lua_pushnil(L); + lua_rawsetp(L, -2, &DFHACK_LUA_VS_TOKEN); + lua_pushnil(L); + lua_setfield(L, -2, "_native"); + + lua_getfield(L, -1, "onDestroy"); + if (lua_isnil(L, -1)) + return 0; + + lua_pushvalue(L, -2); + lua_call(L, 1, 0); + return 0; +} + +void dfhack_lua_viewscreen::update_focus(lua_State *L, int idx) +{ + lua_getfield(L, idx, "text_input_mode"); + text_input_mode = lua_toboolean(L, -1); + lua_pop(L, 1); + + lua_getfield(L, idx, "focus_path"); + auto str = lua_tostring(L, -1); + if (!str) str = ""; + focus = str; + lua_pop(L, 1); + + if (focus.empty()) + focus = "lua"; + else + focus = "lua/"+focus; +} + +int dfhack_lua_viewscreen::do_render(lua_State *L) +{ + auto self = get_self(L); + if (!self) return 0; + + lua_getfield(L, -1, "onRender"); + + if (lua_isnil(L, -1)) + { + Screen::clear(); + return 0; + } + + lua_pushvalue(L, -2); + lua_call(L, 1, 0); + return 0; +} + +int dfhack_lua_viewscreen::do_notify(lua_State *L) +{ + int args = lua_gettop(L); + + auto self = get_self(L); + if (!self) return 0; + + lua_pushvalue(L, 2); + lua_gettable(L, -2); + if (lua_isnil(L, -1)) + return 0; + + // self field args table fn -> table fn table args + lua_replace(L, 1); + lua_copy(L, -1, 2); + lua_insert(L, 1); + lua_call(L, args-1, 1); + + self->update_focus(L, 1); + return 1; +} + +int dfhack_lua_viewscreen::do_input(lua_State *L) +{ + auto self = get_self(L); + if (!self) return 0; + + auto keys = (std::set<df::interface_key>*)lua_touserdata(L, 2); + + lua_getfield(L, -1, "onInput"); + + if (lua_isnil(L, -1)) + { + if (keys->count(interface_key::LEAVESCREEN)) + Screen::dismiss(self); + + return 0; + } + + lua_pushvalue(L, -2); + + lua_createtable(L, 0, keys->size()+3); + + for (auto it = keys->begin(); it != keys->end(); ++it) + { + auto key = *it; + + if (auto name = enum_item_raw_key(key)) + lua_pushstring(L, name); + else + lua_pushinteger(L, key); + + lua_pushboolean(L, true); + lua_rawset(L, -3); + + if (key >= interface_key::STRING_A000 && + key <= interface_key::STRING_A255) + { + lua_pushinteger(L, key - interface_key::STRING_A000); + lua_setfield(L, -2, "_STRING"); + } + } + + if (enabler && enabler->tracking_on) + { + if (enabler->mouse_lbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_L"); + } + if (enabler->mouse_rbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_R"); + } + } + + lua_call(L, 2, 0); + self->update_focus(L, -1); + return 0; +} + +dfhack_lua_viewscreen::dfhack_lua_viewscreen(lua_State *L, int table_idx) +{ + assert(Lua::IsCoreContext(L)); + + Lua::PushDFObject(L, (df::viewscreen*)this); + lua_setfield(L, table_idx, "_native"); + lua_pushlightuserdata(L, this); + lua_rawsetp(L, table_idx, &DFHACK_LUA_VS_TOKEN); + + lua_pushvalue(L, table_idx); + lua_rawsetp(L, LUA_REGISTRYINDEX, this); + + update_focus(L, table_idx); +} + +dfhack_lua_viewscreen::~dfhack_lua_viewscreen() +{ + safe_call_lua(do_destroy, 0, 0); +} + +void dfhack_lua_viewscreen::render() +{ + if (Screen::isDismissed(this)) return; + + dfhack_viewscreen::render(); + + safe_call_lua(do_render, 0, 0); +} + +void dfhack_lua_viewscreen::logic() +{ + if (Screen::isDismissed(this)) return; + + dfhack_viewscreen::logic(); + + lua_pushstring(Lua::Core::State, "onIdle"); + safe_call_lua(do_notify, 1, 0); +} + +void dfhack_lua_viewscreen::help() +{ + if (Screen::isDismissed(this)) return; + + lua_pushstring(Lua::Core::State, "onHelp"); + safe_call_lua(do_notify, 1, 0); +} + +void dfhack_lua_viewscreen::resize(int w, int h) +{ + if (Screen::isDismissed(this)) return; + + auto L = Lua::Core::State; + lua_pushstring(L, "onResize"); + lua_pushinteger(L, w); + lua_pushinteger(L, h); + safe_call_lua(do_notify, 3, 0); +} + +void dfhack_lua_viewscreen::feed(std::set<df::interface_key> *keys) +{ + if (Screen::isDismissed(this)) return; + + lua_pushlightuserdata(Lua::Core::State, keys); + safe_call_lua(do_input, 1, 0); +} + +void dfhack_lua_viewscreen::onShow() +{ + lua_pushstring(Lua::Core::State, "onShow"); + safe_call_lua(do_notify, 1, 0); +} + +void dfhack_lua_viewscreen::onDismiss() +{ + lua_pushstring(Lua::Core::State, "onDismiss"); + safe_call_lua(do_notify, 1, 0); +} diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 308700fb..282157a3 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -948,3 +948,40 @@ std::string DFHack::Units::getCasteProfessionName(int race, int casteid, df::pro return Translation::capitalize(prof, true); } + +int8_t DFHack::Units::getProfessionColor(df::unit *unit, bool ignore_noble) +{ + std::vector<NoblePosition> np; + + if (!ignore_noble && getNoblePositions(&np, unit)) + { + if (np[0].position->flags.is_set(entity_position_flags::COLOR)) + return np[0].position->color[0] + np[0].position->color[2] * 8; + } + + return getCasteProfessionColor(unit->race, unit->caste, unit->profession); +} + +int8_t DFHack::Units::getCasteProfessionColor(int race, int casteid, df::profession pid) +{ + // make sure it's an actual profession + if (pid < 0 || !is_valid_enum_item(pid)) + return 3; + + // If it's not a Peasant, it's hardcoded + if (pid != profession::STANDARD) + return ENUM_ATTR(profession, color, pid); + + if (auto creature = df::creature_raw::find(race)) + { + if (auto caste = vector_get(creature->caste, casteid)) + { + if (caste->flags.is_set(caste_raw_flags::CASTE_COLOR)) + return caste->caste_color[0] + caste->caste_color[2] * 8; + } + return creature->color[0] + creature->color[2] * 8; + } + + // default to dwarven peasant color + return 3; +} diff --git a/library/xml b/library/xml -Subproject ad38c5e96b05fedf16114fd16bd463e933f1358 +Subproject abcb667bc832048552d8cbc8f4830936f8b6339 |
