summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
authorTimothy Collett2012-09-10 11:54:56 -0400
committerTimothy Collett2012-09-10 11:54:56 -0400
commit96abc903abb93b7bf7418f2da3455ee6da5ad942 (patch)
treea5326ef1dbd492086b319c888854e707c59d2a56 /library
parent274d6038adce5797b58cee78a330eb5d639bf59e (diff)
parent21904fd607d0f2037782e28ff7284300663931ab (diff)
downloaddfhack-96abc903abb93b7bf7418f2da3455ee6da5ad942.tar.gz
dfhack-96abc903abb93b7bf7418f2da3455ee6da5ad942.tar.bz2
dfhack-96abc903abb93b7bf7418f2da3455ee6da5ad942.tar.xz
Merge branch 'master' of http://github.com/peterix/dfhack
Diffstat (limited to 'library')
-rw-r--r--library/CMakeLists.txt9
-rw-r--r--library/Console-darwin.cpp2
-rw-r--r--library/Console-linux.cpp20
-rw-r--r--library/Console-windows.cpp2
-rw-r--r--library/Core.cpp156
-rw-r--r--library/DataDefs.cpp8
-rw-r--r--library/LuaApi.cpp240
-rw-r--r--library/LuaTools.cpp176
-rw-r--r--library/LuaTypes.cpp25
-rw-r--r--library/PluginManager.cpp68
-rw-r--r--library/VTableInterpose.cpp249
-rw-r--r--library/include/ColorText.h44
-rw-r--r--library/include/Core.h26
-rw-r--r--library/include/DataDefs.h12
-rw-r--r--library/include/DataFuncs.h15
-rw-r--r--library/include/DataIdentity.h40
-rw-r--r--library/include/LuaTools.h47
-rw-r--r--library/include/MemAccess.h3
-rw-r--r--library/include/PluginManager.h25
-rw-r--r--library/include/VTableInterpose.h173
-rw-r--r--library/include/modules/Buildings.h5
-rw-r--r--library/include/modules/Gui.h8
-rw-r--r--library/include/modules/Screen.h176
-rw-r--r--library/include/modules/Units.h3
-rw-r--r--library/lua/dfhack.lua46
-rw-r--r--library/lua/gui.lua400
-rw-r--r--library/lua/gui/dwarfmode.lua279
-rw-r--r--library/lua/utils.lua32
-rw-r--r--library/modules/Buildings.cpp41
-rw-r--r--library/modules/Gui.cpp22
-rw-r--r--library/modules/Job.cpp2
-rw-r--r--library/modules/Materials.cpp6
-rw-r--r--library/modules/Screen.cpp604
-rw-r--r--library/modules/Units.cpp37
m---------library/xml0
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