summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
authorTimothy Collett2012-09-10 11:56:23 -0400
committerTimothy Collett2012-09-10 11:56:23 -0400
commitccefd02ee3921a84a4a053bb163968530b1b2346 (patch)
treebb003b30eacde789646a25dd696cc1fd38d6dce6 /library
parent96abc903abb93b7bf7418f2da3455ee6da5ad942 (diff)
parent8ab615f6d0dce167a1952c4684922a7e48263c23 (diff)
downloaddfhack-ccefd02ee3921a84a4a053bb163968530b1b2346.tar.gz
dfhack-ccefd02ee3921a84a4a053bb163968530b1b2346.tar.bz2
dfhack-ccefd02ee3921a84a4a053bb163968530b1b2346.tar.xz
Merge branch 'master' of git://github.com/angavrilov/dfhack
Diffstat (limited to 'library')
-rw-r--r--library/Core.cpp25
-rw-r--r--library/DataDefs.cpp8
-rw-r--r--library/LuaApi.cpp75
-rw-r--r--library/LuaTools.cpp37
-rw-r--r--library/LuaTypes.cpp4
-rw-r--r--library/PluginManager.cpp24
-rw-r--r--library/Process-darwin.cpp8
-rw-r--r--library/Process-linux.cpp8
-rw-r--r--library/Process-windows.cpp5
-rw-r--r--library/VTableInterpose.cpp292
-rw-r--r--library/include/BitArray.h4
-rw-r--r--library/include/DataDefs.h2
-rw-r--r--library/include/DataIdentity.h2
-rw-r--r--library/include/MemAccess.h3
-rw-r--r--library/include/PluginManager.h4
-rw-r--r--library/include/VTableInterpose.h14
-rw-r--r--library/include/modules/Gui.h22
-rw-r--r--library/include/modules/MapCache.h10
-rw-r--r--library/include/modules/Maps.h10
-rw-r--r--library/include/modules/Screen.h6
-rw-r--r--library/include/modules/Units.h20
-rw-r--r--library/lua/dfhack.lua27
-rw-r--r--library/lua/gui.lua25
-rw-r--r--library/lua/gui/dialogs.lua271
-rw-r--r--library/lua/gui/dwarfmode.lua132
-rw-r--r--library/lua/utils.lua13
-rw-r--r--library/modules/Buildings.cpp2
-rw-r--r--library/modules/Gui.cpp184
-rw-r--r--library/modules/Maps.cpp95
-rw-r--r--library/modules/Screen.cpp39
-rw-r--r--library/modules/Units.cpp479
-rw-r--r--library/modules/World.cpp4
m---------library/xml0
33 files changed, 1755 insertions, 99 deletions
diff --git a/library/Core.cpp b/library/Core.cpp
index 6a0dea7c..735359a7 100644
--- a/library/Core.cpp
+++ b/library/Core.cpp
@@ -219,28 +219,30 @@ static std::string getScriptHelp(std::string path, std::string helpprefix)
return "No help available.";
}
-static std::map<string,string> listScripts(PluginManager *plug_mgr, std::string path)
+static void listScripts(PluginManager *plug_mgr, std::map<string,string> &pset, std::string path, bool all, std::string prefix = "")
{
std::vector<string> files;
getdir(path, files);
- std::map<string,string> pset;
for (size_t i = 0; i < files.size(); i++)
{
if (hasEnding(files[i], ".lua"))
{
std::string help = getScriptHelp(path + files[i], "-- ");
- pset[files[i].substr(0, files[i].size()-4)] = help;
+ pset[prefix + files[i].substr(0, files[i].size()-4)] = help;
}
else if (plug_mgr->eval_ruby && hasEnding(files[i], ".rb"))
{
std::string help = getScriptHelp(path + files[i], "# ");
- pset[files[i].substr(0, files[i].size()-3)] = help;
+ pset[prefix + files[i].substr(0, files[i].size()-3)] = help;
+ }
+ else if (all && !files[i].empty() && files[i][0] != '.')
+ {
+ listScripts(plug_mgr, pset, path+files[i]+"/", all, prefix+files[i]+"/");
}
}
- return pset;
}
static bool fileExists(std::string path)
@@ -335,7 +337,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
con.print("Basic commands:\n"
" help|?|man - This text.\n"
" help COMMAND - Usage help for the given command.\n"
- " ls|dir [PLUGIN] - List available commands. Optionally for single plugin.\n"
+ " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n"
" cls - Clear the console.\n"
" fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n"
@@ -469,6 +471,12 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
}
else if(first == "ls" || first == "dir")
{
+ bool all = false;
+ if (parts.size() && parts[0] == "-a")
+ {
+ all = true;
+ vector_erase_at(parts, 0);
+ }
if(parts.size())
{
string & plugname = parts[0];
@@ -491,7 +499,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
con.print(
"builtin:\n"
" help|?|man - This text or help specific to a plugin.\n"
- " ls [PLUGIN] - List available commands. Optionally for single plugin.\n"
+ " ls [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n"
" cls - Clear the console.\n"
" fpause - Force DF to pause.\n"
" die - Force DF to close immediately\n"
@@ -523,7 +531,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str());
con.reset_color();
}
- auto scripts = listScripts(plug_mgr, getHackPath() + "scripts/");
+ std::map<string, string> scripts;
+ listScripts(plug_mgr, scripts, getHackPath() + "scripts/", all);
if (!scripts.empty())
{
con.print("\nscripts:\n");
diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp
index d6604cdb..fa2aacf7 100644
--- a/library/DataDefs.cpp
+++ b/library/DataDefs.cpp
@@ -218,8 +218,10 @@ 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();
+ for (auto it = interpose_list.begin(); it != interpose_list.end(); ++it)
+ if (it->second)
+ it->second->on_host_delete(this);
+ interpose_list.clear();
}
/* Vtable name to identity lookup. */
@@ -372,7 +374,7 @@ void DFHack::bitfieldToString(std::vector<std::string> *pvec, const void *p,
unsigned size, const bitfield_item_info *items)
{
for (unsigned i = 0; i < size; i++) {
- int value = getBitfieldField(p, i, std::min(1,items[i].size));
+ int value = getBitfieldField(p, i, std::max(1,items[i].size));
if (value) {
std::string name = format_key(items[i].name, i);
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 00d4c517..d39a945d 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -77,6 +77,9 @@ distribution.
#include "df/job_material_category.h"
#include "df/burrow.h"
#include "df/building_civzonest.h"
+#include "df/region_map_entry.h"
+#include "df/flow_info.h"
+#include "df/unit_misc_trait.h"
#include <lua.h>
#include <lauxlib.h>
@@ -726,6 +729,7 @@ static std::string getOSType()
}
static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); }
+static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); }
static std::string getDFPath() { return Core::getInstance().p->getPath(); }
static std::string getHackPath() { return Core::getInstance().getHackPath(); }
@@ -737,6 +741,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType),
WRAP(getDFVersion),
WRAP(getDFPath),
+ WRAP(getTickCount),
WRAP(getHackPath),
WRAP(isWorldLoaded),
WRAP(isMapLoaded),
@@ -755,7 +760,9 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem),
WRAPM(Gui, showAnnouncement),
+ WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement),
+ WRAPM(Gui, showAutoAnnouncement),
{ NULL, NULL }
};
@@ -807,12 +814,20 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getVisibleName),
WRAPM(Units, getIdentity),
WRAPM(Units, getNemesis),
+ WRAPM(Units, isCrazed),
+ WRAPM(Units, isOpposedToLife),
+ WRAPM(Units, hasExtravision),
+ WRAPM(Units, isBloodsucker),
+ WRAPM(Units, isMischievous),
+ WRAPM(Units, getMiscTrait),
WRAPM(Units, isDead),
WRAPM(Units, isAlive),
WRAPM(Units, isSane),
WRAPM(Units, isDwarf),
WRAPM(Units, isCitizen),
WRAPM(Units, getAge),
+ WRAPM(Units, getEffectiveSkill),
+ WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName),
WRAPM(Units, getProfessionColor),
@@ -911,9 +926,17 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canWalkBetween),
+ WRAPM(Maps, spawnFlow),
{ NULL, NULL }
};
+static int maps_isValidTilePos(lua_State *L)
+{
+ auto pos = CheckCoordXYZ(L, 1, true);
+ lua_pushboolean(L, Maps::isValidTilePos(pos));
+ return 1;
+}
+
static int maps_getTileBlock(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
@@ -921,6 +944,13 @@ static int maps_getTileBlock(lua_State *L)
return 1;
}
+static int maps_ensureTileBlock(lua_State *L)
+{
+ auto pos = CheckCoordXYZ(L, 1, true);
+ Lua::PushDFObject(L, Maps::ensureTileBlock(pos));
+ return 1;
+}
+
static int maps_getRegionBiome(lua_State *L)
{
auto pos = CheckCoordXY(L, 1, true);
@@ -931,12 +961,13 @@ static int maps_getRegionBiome(lua_State *L)
static int maps_getTileBiomeRgn(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
- Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
- return 1;
+ return Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
}
static const luaL_Reg dfhack_maps_funcs[] = {
+ { "isValidTilePos", maps_isValidTilePos },
{ "getTileBlock", maps_getTileBlock },
+ { "ensureTileBlock", maps_ensureTileBlock },
{ "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn },
{ NULL, NULL }
@@ -1132,6 +1163,45 @@ static int screen_paintTile(lua_State *L)
return 1;
}
+static int screen_readTile(lua_State *L)
+{
+ int x = luaL_checkint(L, 1);
+ int y = luaL_checkint(L, 2);
+ Pen pen = Screen::readTile(x, y);
+
+ if (!pen.valid())
+ {
+ lua_pushnil(L);
+ }
+ else
+ {
+ lua_newtable(L);
+ lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch");
+ lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg");
+ lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg");
+ lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold");
+
+ if (pen.tile)
+ {
+ lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile");
+
+ switch (pen.tile_mode) {
+ case Pen::CharColor:
+ lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color");
+ break;
+ case Pen::TileColor:
+ lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg");
+ lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return 1;
+}
+
static int screen_paintString(lua_State *L)
{
Pen pen;
@@ -1236,6 +1306,7 @@ static const luaL_Reg dfhack_screen_funcs[] = {
{ "getMousePos", screen_getMousePos },
{ "getWindowSize", screen_getWindowSize },
{ "paintTile", screen_paintTile },
+ { "readTile", screen_readTile },
{ "paintString", screen_paintString },
{ "fillRect", screen_fillRect },
{ "findGraphicsTile", screen_findGraphicsTile },
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp
index 7c2c8f8d..a283d215 100644
--- a/library/LuaTools.cpp
+++ b/library/LuaTools.cpp
@@ -1211,6 +1211,39 @@ static int dfhack_open_plugin(lua_State *L)
return 0;
}
+static int dfhack_curry_wrap(lua_State *L)
+{
+ int nargs = lua_gettop(L);
+ int ncurry = lua_tointeger(L, lua_upvalueindex(1));
+ int scount = nargs + ncurry;
+
+ luaL_checkstack(L, ncurry, "stack overflow in curry");
+
+ // Insert values in O(N+M) by first shifting the existing data
+ lua_settop(L, scount);
+ for (int i = 0; i < nargs; i++)
+ lua_copy(L, nargs-i, scount-i);
+ for (int i = 1; i <= ncurry; i++)
+ lua_copy(L, lua_upvalueindex(i+1), i);
+
+ lua_callk(L, scount-1, LUA_MULTRET, 0, lua_gettop);
+
+ return lua_gettop(L);
+}
+
+static int dfhack_curry(lua_State *L)
+{
+ luaL_checkany(L, 1);
+ if (lua_isnil(L, 1))
+ luaL_argerror(L, 1, "nil function in curry");
+ if (lua_gettop(L) == 1)
+ return 1;
+ lua_pushinteger(L, lua_gettop(L));
+ lua_insert(L, 1);
+ lua_pushcclosure(L, dfhack_curry_wrap, lua_gettop(L));
+ return 1;
+}
+
bool Lua::IsCoreContext(lua_State *state)
{
// This uses a private field of the lua state to
@@ -1234,6 +1267,7 @@ static const luaL_Reg dfhack_funcs[] = {
{ "call_with_finalizer", dfhack_call_with_finalizer },
{ "with_suspend", lua_dfhack_with_suspend },
{ "open_plugin", dfhack_open_plugin },
+ { "curry", dfhack_curry },
{ NULL, NULL }
};
@@ -1546,6 +1580,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
lua_setfield(state, -2, "BASE_G");
+ lua_pushstring(state, DFHACK_VERSION);
+ lua_setfield(state, -2, "VERSION");
+
lua_pushboolean(state, IsCoreContext(state));
lua_setfield(state, -2, "is_core_context");
diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp
index e7197796..9f2689fa 100644
--- a/library/LuaTypes.cpp
+++ b/library/LuaTypes.cpp
@@ -894,7 +894,7 @@ static int meta_bitfield_len(lua_State *state)
static void read_bitfield(lua_State *state, uint8_t *ptr, bitfield_identity *id, int idx)
{
- int size = id->getBits()[idx].size;
+ int size = std::max(1, id->getBits()[idx].size);
int value = getBitfieldField(ptr, idx, size);
if (size <= 1)
@@ -951,7 +951,7 @@ static int meta_bitfield_newindex(lua_State *state)
}
int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write");
- int size = id->getBits()[idx].size;
+ int size = std::max(1, id->getBits()[idx].size);
if (lua_isboolean(state, 3) || lua_isnil(state, 3))
setBitfieldField(ptr, idx, size, lua_toboolean(state, 3));
diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp
index d8b9ff27..ceb644e6 100644
--- a/library/PluginManager.cpp
+++ b/library/PluginManager.cpp
@@ -186,14 +186,17 @@ Plugin::~Plugin()
bool Plugin::load(color_ostream &con)
{
- RefAutolock lock(access);
- if(state == PS_BROKEN)
- {
- return false;
- }
- else if(state == PS_LOADED)
{
- return true;
+ RefAutolock lock(access);
+ if(state == PS_LOADED)
+ {
+ return true;
+ }
+ else if(state != PS_UNLOADED)
+ {
+ return false;
+ }
+ state = PS_LOADING;
}
// enter suspend
CoreSuspender suspend;
@@ -202,6 +205,7 @@ bool Plugin::load(color_ostream &con)
if(!plug)
{
con.printerr("Can't load plugin %s\n", filename.c_str());
+ RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
@@ -211,6 +215,7 @@ bool Plugin::load(color_ostream &con)
{
con.printerr("Plugin %s has no name or version.\n", filename.c_str());
ClosePlugin(plug);
+ RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
@@ -219,9 +224,11 @@ bool Plugin::load(color_ostream &con)
con.printerr("Plugin %s was not built for this version of DFHack.\n"
"Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, DFHACK_VERSION);
ClosePlugin(plug);
+ RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
+ RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init)
{
@@ -273,8 +280,11 @@ bool Plugin::unload(color_ostream &con)
}
// wait for all calls to finish
access->wait();
+ state = PS_UNLOADING;
+ access->unlock();
// enter suspend
CoreSuspender suspend;
+ access->lock();
// notify plugin about shutdown, if it has a shutdown function
command_result cr = CR_OK;
if(plugin_shutdown)
diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp
index 5a97d9e0..3893cfc5 100644
--- a/library/Process-darwin.cpp
+++ b/library/Process-darwin.cpp
@@ -27,6 +27,7 @@ distribution.
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
+#include <sys/time.h>
#include <mach-o/dyld.h>
@@ -262,6 +263,13 @@ bool Process::getThreadIDs(vector<uint32_t> & threads )
return true;
}
+uint32_t Process::getTickCount()
+{
+ struct timeval tp;
+ gettimeofday(&tp, NULL);
+ return (tp.tv_sec * 1000) + (tp.tv_usec / 1000);
+}
+
string Process::getPath()
{
char path[1024];
diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp
index fe864784..4a66470f 100644
--- a/library/Process-linux.cpp
+++ b/library/Process-linux.cpp
@@ -27,6 +27,7 @@ distribution.
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
+#include <sys/time.h>
#include <string>
#include <vector>
@@ -192,6 +193,13 @@ bool Process::getThreadIDs(vector<uint32_t> & threads )
return true;
}
+uint32_t Process::getTickCount()
+{
+ struct timeval tp;
+ gettimeofday(&tp, NULL);
+ return (tp.tv_sec * 1000) + (tp.tv_usec / 1000);
+}
+
string Process::getPath()
{
const char * cwd_name = "/proc/self/cwd";
diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp
index 7eb6ff5f..db58c4d3 100644
--- a/library/Process-windows.cpp
+++ b/library/Process-windows.cpp
@@ -410,6 +410,11 @@ string Process::doReadClassName (void * vptr)
return raw;
}
+uint32_t Process::getTickCount()
+{
+ return GetTickCount();
+}
+
string Process::getPath()
{
HMODULE hmod;
diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp
index 3725ccba..583ef518 100644
--- a/library/VTableInterpose.cpp
+++ b/library/VTableInterpose.cpp
@@ -48,6 +48,26 @@ struct MSVC_MPTR {
intptr_t this_shift;
};
+static uint32_t *follow_jmp(void *ptr)
+{
+ uint8_t *p = (uint8_t*)ptr;
+
+ for (;;)
+ {
+ switch (*p)
+ {
+ case 0xE9:
+ p += 5 + *(int32_t*)(p+1);
+ break;
+ case 0xEB:
+ p += 2 + *(int8_t*)(p+1);
+ break;
+ default:
+ return (uint32_t*)p;
+ }
+ }
+}
+
bool DFHack::is_vmethod_pointer_(void *pptr)
{
auto pobj = (MSVC_MPTR*)pptr;
@@ -55,7 +75,7 @@ bool DFHack::is_vmethod_pointer_(void *pptr)
// MSVC implements pointers to vmethods via thunks.
// This expects that they all follow a very specific pattern.
- auto pval = (unsigned*)pobj->method;
+ auto pval = follow_jmp(pobj->method);
switch (pval[0]) {
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
@@ -71,7 +91,7 @@ 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;
+ auto pval = follow_jmp(pobj->method);
switch (pval[0]) {
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
return 0;
@@ -154,6 +174,73 @@ bool virtual_identity::set_vmethod_ptr(int idx, void *ptr)
return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*));
}
+/*
+ VMethod interposing data structures.
+
+ In order to properly support adding and removing hooks,
+ it is necessary to track them. This is what this class
+ is for. The task is further complicated by propagating
+ hooks to child classes that use exactly the same original
+ vmethod implementation.
+
+ Every applied link contains in the saved_chain field a
+ pointer to the next vmethod body that should be called
+ by the hook the link represents. This is the actual
+ control flow structure that needs to be maintained.
+
+ There also are connections between link objects themselves,
+ which constitute the bookkeeping for doing that. Finally,
+ every link is associated with a fixed virtual_identity host,
+ which represents the point in the class hierarchy where
+ the hook is applied.
+
+ When there are no subclasses (i.e. only one host), the
+ structures look like this:
+
+ +--------------+ +------------+
+ | link1 |-next------->| link2 |-next=NULL
+ |s_c: original |<-------prev-|s_c: $link1 |<--+
+ +--------------+ +------------+ |
+ |
+ host->interpose_list[vmethod_idx] ------+
+ vtable: $link2
+
+ The original vtable entry is stored in the saved_chain of the
+ first link. The interpose_list map points to the last one.
+ The hooks are called in order: link2 -> link1 -> original.
+
+ When there are subclasses that use the same vmethod, but don't
+ hook it, the topmost link gets a set of the child_hosts, and
+ the hosts have the link added to their interpose_list:
+
+ +--------------+ +----------------+
+ | link0 @host0 |<--+-interpose_list-| host1 |
+ | |-child_hosts-+----->| vtable: $link |
+ +--------------+ | | +----------------+
+ | |
+ | | +----------------+
+ +-interpose_list-| host2 |
+ +----->| vtable: $link |
+ +----------------+
+
+ When a child defines its own hook, the child_hosts link is
+ severed and replaced with a child_next pointer to the new
+ hook. The hook still points back the chain with prev.
+ All child links to subclasses of host2 are migrated from
+ link1 to link2.
+
+ +--------------+-next=NULL +--------------+-next=NULL
+ | link1 @host1 |-child_next------->| link2 @host2 |-child_*--->subclasses
+ | |<-------------prev-|s_c: $link1 |
+ +--------------+<-------+ +--------------+<-------+
+ | |
+ +--------------+ | +--------------+ |
+ | host1 |-i_list-+ | host2 |-i_list-+
+ |vtable: $link1| |vtable: $link2|
+ +--------------+ +--------------+
+
+ */
+
void VMethodInterposeLinkBase::set_chain(void *chain)
{
saved_chain = chain;
@@ -162,7 +249,7 @@ void VMethodInterposeLinkBase::set_chain(void *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)
+ applied(false), saved_chain(NULL), next(NULL), prev(NULL)
{
if (vmethod_idx < 0 || interpose_method == NULL)
{
@@ -179,8 +266,83 @@ VMethodInterposeLinkBase::~VMethodInterposeLinkBase()
remove();
}
-bool VMethodInterposeLinkBase::apply()
+VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_identity *id)
+{
+ auto item = id->interpose_list[vmethod_idx];
+ if (!item)
+ return NULL;
+
+ if (item->host != id)
+ return NULL;
+ while (item->prev && item->prev->host == id)
+ item = item->prev;
+
+ return item;
+}
+
+void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr)
{
+ auto &children = cur->getChildren();
+
+ for (size_t i = 0; i < children.size(); i++)
+ {
+ auto child = static_cast<virtual_identity*>(children[i]);
+ auto base = get_first_interpose(child);
+
+ if (base)
+ {
+ assert(base->prev == NULL);
+
+ if (base->saved_chain != vmptr)
+ continue;
+
+ child_next.insert(base);
+ }
+ else
+ {
+ void *cptr = child->get_vmethod_ptr(vmethod_idx);
+ if (cptr != vmptr)
+ continue;
+
+ child_hosts.insert(child);
+ find_child_hosts(child, vmptr);
+ }
+ }
+}
+
+void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
+{
+ if (from == host)
+ {
+ // When in own host, fully delete
+ remove();
+ }
+ else
+ {
+ // Otherwise, drop the link to that child:
+ assert(child_hosts.count(from) != 0 &&
+ from->interpose_list[vmethod_idx] == this);
+
+ // Find and restore the original vmethod ptr
+ auto last = this;
+ while (last->prev) last = last->prev;
+
+ from->set_vmethod_ptr(vmethod_idx, last->saved_chain);
+
+ // Unlink the chains
+ child_hosts.erase(from);
+ from->interpose_list[vmethod_idx] = NULL;
+ }
+}
+
+bool VMethodInterposeLinkBase::apply(bool enable)
+{
+ if (!enable)
+ {
+ remove();
+ return true;
+ }
+
if (is_applied())
return true;
if (!host->vtable_ptr)
@@ -188,33 +350,73 @@ bool VMethodInterposeLinkBase::apply()
// 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;
+ VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
- 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;
- }
+ assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr
+ set_chain(old_ptr);
+
if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
+ {
+ set_chain(NULL);
return false;
+ }
- set_chain(old_ptr);
- host->interpose_list.push_back(this);
+ // Push the current link into the home host
+ applied = true;
+ host->interpose_list[vmethod_idx] = this;
+ prev = old_link;
- // Link into the chain if any
- if (old_link)
+ child_hosts.clear();
+ child_next.clear();
+
+ if (old_link && old_link->host == host)
{
+ // If the old link is home, just push into the plain chain
+ assert(old_link->next == NULL);
old_link->next = this;
- prev = old_link;
+
+ // Child links belong to the topmost local entry
+ child_hosts.swap(old_link->child_hosts);
+ child_next.swap(old_link->child_next);
+ }
+ else
+ {
+ // If creating a new local chain, find children with same vmethod
+ find_child_hosts(host, old_ptr);
+
+ if (old_link)
+ {
+ // Enter the child chain set
+ assert(old_link->child_hosts.count(host));
+ old_link->child_hosts.erase(host);
+ old_link->child_next.insert(this);
+
+ // Subtract our own children from the parent's sets
+ for (auto it = child_next.begin(); it != child_next.end(); ++it)
+ old_link->child_next.erase(*it);
+ for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
+ old_link->child_hosts.erase(*it);
+ }
+ }
+
+ // Chain subclass hooks
+ for (auto it = child_next.begin(); it != child_next.end(); ++it)
+ {
+ auto nlink = *it;
+ assert(nlink->saved_chain == old_ptr && nlink->prev == old_link);
+ nlink->set_chain(interpose_method);
+ nlink->prev = this;
+ }
+
+ // Chain passive subclass hosts
+ for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
+ {
+ auto nhost = *it;
+ assert(nhost->interpose_list[vmethod_idx] == old_link);
+ nhost->set_vmethod_ptr(vmethod_idx, interpose_method);
+ nhost->interpose_list[vmethod_idx] = this;
}
return true;
@@ -225,25 +427,57 @@ 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
+ // Remove the link from prev to this
if (prev)
- prev->next = next;
+ {
+ if (prev->host == host)
+ prev->next = next;
+ else
+ {
+ prev->child_next.erase(this);
+
+ if (next)
+ prev->child_next.insert(next);
+ }
+ }
if (next)
{
next->set_chain(saved_chain);
next->prev = prev;
+
+ assert(child_next.empty() && child_hosts.empty());
}
else
{
+ // Remove from the list in the identity and vtable
+ host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain);
+
+ for (auto it = child_next.begin(); it != child_next.end(); ++it)
+ {
+ auto nlink = *it;
+ assert(nlink->saved_chain == interpose_method && nlink->prev == this);
+ nlink->set_chain(saved_chain);
+ nlink->prev = prev;
+ if (prev)
+ prev->child_next.insert(nlink);
+ }
+
+ for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
+ {
+ auto nhost = *it;
+ assert(nhost->interpose_list[vmethod_idx] == this);
+ nhost->interpose_list[vmethod_idx] = prev;
+ nhost->set_vmethod_ptr(vmethod_idx, saved_chain);
+ if (prev)
+ prev->child_hosts.insert(nhost);
+ }
}
+ applied = false;
prev = next = NULL;
+ child_next.clear();
+ child_hosts.clear();
set_chain(NULL);
}
diff --git a/library/include/BitArray.h b/library/include/BitArray.h
index fd9bd98f..ff68ea1d 100644
--- a/library/include/BitArray.h
+++ b/library/include/BitArray.h
@@ -64,7 +64,7 @@ namespace DFHack
if (newsize == size)
return;
uint8_t* mem = (uint8_t *) realloc(bits, newsize);
- if(!mem)
+ if(!mem && newsize != 0)
throw std::bad_alloc();
bits = mem;
if (newsize > size)
@@ -207,7 +207,7 @@ namespace DFHack
else
{
T* mem = (T*) realloc(m_data, sizeof(T)*new_size);
- if(!mem)
+ if(!mem && new_size != 0)
throw std::bad_alloc();
m_data = mem;
}
diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h
index 6b3aeb3e..61d5dec4 100644
--- a/library/include/DataDefs.h
+++ b/library/include/DataDefs.h
@@ -303,7 +303,7 @@ namespace DFHack
void *vtable_ptr;
friend class VMethodInterposeLinkBase;
- std::vector<VMethodInterposeLinkBase*> interpose_list;
+ std::map<int,VMethodInterposeLinkBase*> interpose_list;
protected:
virtual void doInit(Core *core);
diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h
index 0f5fd9e7..21dc68d1 100644
--- a/library/include/DataIdentity.h
+++ b/library/include/DataIdentity.h
@@ -390,7 +390,7 @@ namespace df
}
virtual bool resize(void *ptr, int size) {
- ((container*)ptr)->resize(size);
+ ((container*)ptr)->resize(size*8);
return true;
}
diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h
index 0e5f618e..a226018a 100644
--- a/library/include/MemAccess.h
+++ b/library/include/MemAccess.h
@@ -281,6 +281,9 @@ namespace DFHack
/// get the DF Process FilePath
std::string getPath();
+ /// millisecond tick count, exactly as DF uses
+ uint32_t getTickCount();
+
/// modify permisions of memory range
bool setPermisions(const t_memrange & range,const t_memrange &trgrange);
diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h
index 25b05ad4..38f0e2e5 100644
--- a/library/include/PluginManager.h
+++ b/library/include/PluginManager.h
@@ -128,7 +128,9 @@ namespace DFHack
{
PS_UNLOADED,
PS_LOADED,
- PS_BROKEN
+ PS_BROKEN,
+ PS_LOADING,
+ PS_UNLOADING
};
friend class PluginManager;
friend class RPCService;
diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h
index bb7a37ce..7ba6b67a 100644
--- a/library/include/VTableInterpose.h
+++ b/library/include/VTableInterpose.h
@@ -134,22 +134,32 @@ namespace DFHack
1) Allow multiple hooks into the same vmethod
2) Auto-remove hooks when a plugin is unloaded.
*/
+ friend class virtual_identity;
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
+ bool applied;
void *saved_chain; // Previous pointer to the code
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
+ // inherited vtable members
+ std::set<virtual_identity*> child_hosts;
+ std::set<VMethodInterposeLinkBase*> child_next;
+
void set_chain(void *chain);
+ void on_host_delete(virtual_identity *host);
+
+ VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
+ void find_child_hosts(virtual_identity *cur, void *vmptr);
public:
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr);
~VMethodInterposeLinkBase();
- bool is_applied() { return saved_chain != NULL; }
- bool apply();
+ bool is_applied() { return applied; }
+ bool apply(bool enable = true);
void remove();
};
diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h
index 273d84ce..97e8bd42 100644
--- a/library/include/modules/Gui.h
+++ b/library/include/modules/Gui.h
@@ -32,6 +32,7 @@ distribution.
#include "DataDefs.h"
#include "df/init.h"
#include "df/ui.h"
+#include "df/announcement_type.h"
namespace df {
struct viewscreen;
@@ -92,11 +93,32 @@ namespace DFHack
// Show a plain announcement, or a titan-style popup message
DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true);
+ DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showPopupAnnouncement(std::string message, int color = 7, bool bright = true);
+ // Show an announcement with effects determined by announcements.txt
+ DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
+
/*
* Cursor and window coords
*/
+ DFHACK_EXPORT df::coord getViewportPos();
+ DFHACK_EXPORT df::coord getCursorPos();
+
+ static const int AREA_MAP_WIDTH = 23;
+ static const int MENU_WIDTH = 30;
+
+ struct DwarfmodeDims {
+ int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2;
+ int y1, y2;
+ bool menu_on, area_on, menu_forced;
+ };
+
+ DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();
+
+ DFHACK_EXPORT void resetDwarfmodeView(bool pause = false);
+ DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false);
+
DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z);
DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z);
diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h
index 109a20a4..262e70bb 100644
--- a/library/include/modules/MapCache.h
+++ b/library/include/modules/MapCache.h
@@ -253,6 +253,8 @@ public:
bool is_valid() { return valid; }
df::map_block *getRaw() { return block; }
+ bool Allocate();
+
MapCache *getParent() { return parent; }
private:
@@ -262,6 +264,8 @@ private:
MapCache *parent;
df::map_block *block;
+ void init();
+
int biomeIndexAt(df::coord2d p);
bool valid;
@@ -347,6 +351,12 @@ class DFHACK_EXPORT MapCache
return BlockAt(df::coord(coord.x>>4,coord.y>>4,coord.z));
}
+ bool ensureBlockAt(df::coord coord)
+ {
+ Block *b = BlockAtTile(coord);
+ return b ? b->Allocate() : false;
+ }
+
df::tiletype baseTiletypeAt (DFCoord tilecoord)
{
Block *b = BlockAtTile(tilecoord);
diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h
index e63eef73..869b2158 100644
--- a/library/include/modules/Maps.h
+++ b/library/include/modules/Maps.h
@@ -50,6 +50,7 @@ distribution.
#include "df/tile_dig_designation.h"
#include "df/tile_traffic.h"
#include "df/feature_init.h"
+#include "df/flow_type.h"
/**
* \defgroup grp_maps Maps module and its types
@@ -232,14 +233,19 @@ extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z);
/// get the position of the map on world map
extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z);
+extern DFHACK_EXPORT bool isValidTilePos(int32_t x, int32_t y, int32_t z);
+inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, pos.z); }
+
/**
* Get the map block or NULL if block is not valid
*/
extern DFHACK_EXPORT df::map_block * getBlock (int32_t blockx, int32_t blocky, int32_t blockz);
extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t z);
+extern DFHACK_EXPORT df::map_block * ensureTileBlock (int32_t x, int32_t y, int32_t z);
inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); }
inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); }
+inline df::map_block * ensureTileBlock (df::coord pos) { return ensureTileBlock(pos.x, pos.y, pos.z); }
extern DFHACK_EXPORT df::tiletype *getTileType(int32_t x, int32_t y, int32_t z);
extern DFHACK_EXPORT df::tile_designation *getTileDesignation(int32_t x, int32_t y, int32_t z);
@@ -258,7 +264,7 @@ inline df::tile_occupancy *getTileOccupancy(df::coord pos) {
/**
* Returns biome info about the specified world region.
*/
-DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos);
+DFHACK_EXPORT df::region_map_entry *getRegionBiome(df::coord2d rgn_pos);
/**
* Returns biome world region coordinates for the given tile within given block.
@@ -272,6 +278,8 @@ inline df::coord2d getTileBiomeRgn(df::coord pos) {
// Enables per-frame updates for liquid flow and/or temperature.
DFHACK_EXPORT void enableBlockUpdates(df::map_block *blk, bool flow = false, bool temperature = false);
+DFHACK_EXPORT df::flow_info *spawnFlow(df::coord pos, df::flow_type type, int mat_type = 0, int mat_index = -1, int density = 100);
+
/// sorts the block event vector into multiple vectors by type
/// mineral veins, what's under ice, blood smears and mud
extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,
diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h
index 492e1eec..4f47205f 100644
--- a/library/include/modules/Screen.h
+++ b/library/include/modules/Screen.h
@@ -65,6 +65,9 @@ namespace DFHack
} tile_mode;
int8_t tile_fg, tile_bg;
+ bool valid() const { return tile >= 0; }
+ bool empty() const { return ch == 0 && tile == 0; }
+
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)
@@ -92,6 +95,9 @@ namespace DFHack
/// Paint one screen tile with the given pen
DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y);
+ /// Retrieves one screen tile from the buffer
+ DFHACK_EXPORT Pen readTile(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);
diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h
index 9003dc3a..65f0b58a 100644
--- a/library/include/modules/Units.h
+++ b/library/include/modules/Units.h
@@ -32,6 +32,10 @@ distribution.
#include "modules/Items.h"
#include "DataDefs.h"
#include "df/unit.h"
+#include "df/misc_trait_type.h"
+#include "df/physical_attribute_type.h"
+#include "df/mental_attribute_type.h"
+#include "df/job_skill.h"
namespace df
{
@@ -41,6 +45,7 @@ namespace df
struct historical_entity;
struct entity_position_assignment;
struct entity_position;
+ struct unit_misc_trait;
}
/**
@@ -208,6 +213,18 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit);
DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit);
DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit);
+DFHACK_EXPORT bool isHidingCurse(df::unit *unit);
+DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr);
+DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr);
+
+DFHACK_EXPORT bool isCrazed(df::unit *unit);
+DFHACK_EXPORT bool isOpposedToLife(df::unit *unit);
+DFHACK_EXPORT bool hasExtravision(df::unit *unit);
+DFHACK_EXPORT bool isBloodsucker(df::unit *unit);
+DFHACK_EXPORT bool isMischievous(df::unit *unit);
+
+DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false);
+
DFHACK_EXPORT bool isDead(df::unit *unit);
DFHACK_EXPORT bool isAlive(df::unit *unit);
DFHACK_EXPORT bool isSane(df::unit *unit);
@@ -216,6 +233,9 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit);
DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
+DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
+DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
+
struct NoblePosition {
df::historical_entity *entity;
df::entity_position_assignment *assignment;
diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua
index 2cbd019a..baf0d42e 100644
--- a/library/lua/dfhack.lua
+++ b/library/lua/dfhack.lua
@@ -46,6 +46,7 @@ end
-- Error handling
safecall = dfhack.safecall
+curry = dfhack.curry
function dfhack.pcall(f, ...)
return xpcall(f, dfhack.onerror, ...)
@@ -83,7 +84,7 @@ function mkmodule(module,env)
error("Not a table in package.loaded["..module.."]")
end
end
- local plugname = string.match(module,'^plugins%.(%w+)$')
+ local plugname = string.match(module,'^plugins%.([%w%-]+)$')
if plugname then
dfhack.open_plugin(pkg,plugname)
end
@@ -118,7 +119,12 @@ function defclass(class,parent)
if parent then
setmetatable(class, parent)
else
- rawset_default(class, { init_fields = rawset_default })
+ rawset_default(class, {
+ init_fields = rawset_default,
+ callback = function(self, name, ...)
+ return dfhack.curry(self[name], self, ...)
+ end
+ })
end
return class
end
@@ -163,6 +169,23 @@ function xyz2pos(x,y,z)
end
end
+function pos2xy(pos)
+ if pos then
+ local x = pos.x
+ if x and x ~= -30000 then
+ return x, pos.y
+ end
+ end
+end
+
+function xy2pos(x,y)
+ if x then
+ return {x=x,y=y}
+ else
+ return {x=-30000,y=-30000}
+ end
+end
+
function safe_index(obj,idx,...)
if obj == nil or idx == nil then
return nil
diff --git a/library/lua/gui.lua b/library/lua/gui.lua
index ac032166..f9b6ab6d 100644
--- a/library/lua/gui.lua
+++ b/library/lua/gui.lua
@@ -18,7 +18,7 @@ function simulateInput(screen,...)
error('Invalid keycode: '..arg)
end
end
- if type(arg) == 'number' then
+ if type(kv) == 'number' then
keys[#keys+1] = kv
end
end
@@ -94,6 +94,9 @@ function Painter:isValidPos()
end
function Painter:viewport(x,y,w,h)
+ if type(x) == 'table' then
+ x,y,w,h = x.x1, x.y1, x.width, x.height
+ end
local x1,y1 = self.x1+x, self.y1+y
local x2,y2 = x1+w-1, y1+h-1
local vp = {
@@ -159,10 +162,10 @@ 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)
+ x1 = math.max(x1+self.x1,self.clip_x1)
+ y1 = math.max(y1+self.y1,self.clip_y1)
+ x2 = math.min(x2+self.x1,self.clip_x2)
+ y2 = math.min(y2+self.y1,self.clip_y2)
dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2)
return self
end
@@ -277,6 +280,9 @@ end
function Screen:onDismiss()
end
+function Screen:onDestroy()
+end
+
function Screen:onResize(w,h)
self:updateLayout()
end
@@ -350,11 +356,16 @@ local function hint_coord(gap,hint)
end
end
+function FramedScreen:getWantedFrameSize()
+ return self.frame_width, self.frame_height
+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 fw, fh = self:getWantedFrameSize()
+ local width = math.min(fw or iw, iw)
+ local height = math.min(fh 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)
diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua
new file mode 100644
index 00000000..eb883465
--- /dev/null
+++ b/library/lua/gui/dialogs.lua
@@ -0,0 +1,271 @@
+-- Some simple dialog screens
+
+local _ENV = mkmodule('gui.dialogs')
+
+local gui = require('gui')
+local utils = require('utils')
+
+local dscreen = dfhack.screen
+
+MessageBox = defclass(MessageBox, gui.FramedScreen)
+
+MessageBox.focus_path = 'MessageBox'
+MessageBox.frame_style = gui.GREY_LINE_FRAME
+
+function MessageBox:init(info)
+ info = info or {}
+ self:init_fields{
+ text = info.text or {},
+ frame_title = info.title,
+ frame_width = info.frame_width,
+ on_accept = info.on_accept,
+ on_cancel = info.on_cancel,
+ on_close = info.on_close,
+ text_pen = info.text_pen
+ }
+ if type(self.text) == 'string' then
+ self.text = utils.split_string(self.text, "\n")
+ end
+ gui.FramedScreen.init(self, info)
+ return self
+end
+
+function MessageBox:getWantedFrameSize()
+ local text = self.text
+ local w = #(self.frame_title or '') + 4
+ w = math.max(w, 20)
+ w = math.max(self.frame_width or w, w)
+ for _, l in ipairs(text) do
+ w = math.max(w, #l)
+ end
+ local h = #text+1
+ if h > 1 then
+ h = h+1
+ end
+ return w+2, #text+2
+end
+
+function MessageBox:onRenderBody(dc)
+ if #self.text > 0 then
+ dc:newline(1):pen(self.text_pen or COLOR_GREY)
+ for _, l in ipairs(self.text or {}) do
+ dc:string(l):newline(1)
+ end
+ end
+
+ if self.on_accept then
+ local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1
+ dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC')
+ dscreen.paintString({fg=COLOR_GREY},x+3,y,'/')
+ dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y')
+ end
+end
+
+function MessageBox:onDestroy()
+ if self.on_close then
+ self.on_close()
+ end
+end
+
+function MessageBox:onInput(keys)
+ if keys.MENU_CONFIRM then
+ self:dismiss()
+ if self.on_accept then
+ self.on_accept()
+ end
+ elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then
+ self:dismiss()
+ if self.on_cancel then
+ self.on_cancel()
+ end
+ end
+end
+
+function showMessage(title, text, tcolor, on_close)
+ mkinstance(MessageBox):init{
+ text = text,
+ title = title,
+ text = text,
+ text_pen = tcolor,
+ on_close = on_close
+ }:show()
+end
+
+function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel)
+ mkinstance(MessageBox):init{
+ title = title,
+ text = text,
+ text_pen = tcolor,
+ on_accept = on_accept,
+ on_cancel = on_cancel,
+ }:show()
+end
+
+InputBox = defclass(InputBox, MessageBox)
+
+InputBox.focus_path = 'InputBox'
+
+function InputBox:init(info)
+ info = info or {}
+ self:init_fields{
+ input = info.input or '',
+ input_pen = info.input_pen,
+ on_input = info.on_input,
+ }
+ MessageBox.init(self, info)
+ self.on_accept = nil
+ return self
+end
+
+function InputBox:getWantedFrameSize()
+ local mw, mh = MessageBox.getWantedFrameSize(self)
+ return mw, mh+2
+end
+
+function InputBox:onRenderBody(dc)
+ MessageBox.onRenderBody(self, dc)
+
+ dc:newline(1)
+ dc:pen(self.input_pen or COLOR_LIGHTCYAN)
+ dc:fill(1,dc:localY(),dc.width-2,dc:localY())
+
+ local cursor = '_'
+ if math.floor(dfhack.getTickCount()/300) % 2 == 0 then
+ cursor = ' '
+ end
+ local txt = self.input .. cursor
+ if #txt > dc.width-2 then
+ txt = string.char(27)..string.sub(txt, #txt-dc.width+4)
+ end
+ dc:string(txt)
+end
+
+function InputBox:onInput(keys)
+ if keys.SELECT then
+ self:dismiss()
+ if self.on_input then
+ self.on_input(self.input)
+ end
+ elseif keys.LEAVESCREEN then
+ self:dismiss()
+ if self.on_cancel then
+ self.on_cancel()
+ end
+ elseif keys._STRING then
+ if keys._STRING == 0 then
+ self.input = string.sub(self.input, 1, #self.input-1)
+ else
+ self.input = self.input .. string.char(keys._STRING)
+ end
+ end
+end
+
+function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width)
+ mkinstance(InputBox):init{
+ title = title,
+ text = text,
+ text_pen = tcolor,
+ input = input,
+ on_input = on_input,
+ on_cancel = on_cancel,
+ frame_width = min_width,
+ }:show()
+end
+
+ListBox = defclass(ListBox, MessageBox)
+
+ListBox.focus_path = 'ListBox'
+
+function ListBox:init(info)
+ info = info or {}
+ self:init_fields{
+ selection = info.selection or 0,
+ choices = info.choices or {},
+ select_pen = info.select_pen,
+ on_input = info.on_input,
+ page_top = 0
+ }
+ MessageBox.init(self, info)
+ self.on_accept = nil
+ return self
+end
+
+function ListBox:getWantedFrameSize()
+ local mw, mh = MessageBox.getWantedFrameSize(self)
+ return mw, mh+#self.choices
+end
+
+function ListBox:onRenderBody(dc)
+ MessageBox.onRenderBody(self, dc)
+
+ dc:newline(1)
+
+ if self.selection>dc.height-3 then
+ self.page_top=self.selection-(dc.height-3)
+ elseif self.selection<self.page_top and self.selection >0 then
+ self.page_top=self.selection-1
+ end
+ for i,entry in ipairs(self.choices) do
+ if type(entry)=="table" then
+ entry=entry[1]
+ end
+ if i>self.page_top then
+ if i == self.selection then
+ dc:pen(self.select_pen or COLOR_LIGHTCYAN)
+ else
+ dc:pen(self.text_pen or COLOR_GREY)
+ end
+ dc:string(entry)
+ dc:newline(1)
+ end
+ end
+end
+function ListBox:moveCursor(delta)
+ local newsel=self.selection+delta
+ if #self.choices ~=0 then
+ if newsel<1 or newsel>#self.choices then
+ newsel=newsel % #self.choices
+ end
+ end
+ self.selection=newsel
+end
+function ListBox:onInput(keys)
+ if keys.SELECT then
+ self:dismiss()
+ local choice=self.choices[self.selection]
+ if self.on_input then
+ self.on_input(self.selection,choice)
+ end
+
+ if choice and choice[2] then
+ choice[2](choice,self.selection) -- maybe reverse the arguments?
+ end
+ elseif keys.LEAVESCREEN then
+ self:dismiss()
+ if self.on_cancel then
+ self.on_cancel()
+ end
+ elseif keys.CURSOR_UP then
+ self:moveCursor(-1)
+ elseif keys.CURSOR_DOWN then
+ self:moveCursor(1)
+ elseif keys.CURSOR_UP_FAST then
+ self:moveCursor(-10)
+ elseif keys.CURSOR_DOWN_FAST then
+ self:moveCursor(10)
+ end
+end
+
+function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width)
+ mkinstance(ListBox):init{
+ title = title,
+ text = text,
+ text_pen = tcolor,
+ choices = choices,
+ on_input = on_input,
+ on_cancel = on_cancel,
+ frame_width = min_width,
+ }:show()
+end
+
+return _ENV
diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua
index c1a8bcb9..661e1559 100644
--- a/library/lua/gui/dwarfmode.lua
+++ b/library/lua/gui/dwarfmode.lua
@@ -6,6 +6,9 @@ local gui = require('gui')
local utils = require('utils')
local dscreen = dfhack.screen
+
+local g_cursor = df.global.cursor
+local g_sel_rect = df.global.selection_rect
local world_map = df.global.world.map
AREA_MAP_WIDTH = 23
@@ -43,8 +46,8 @@ function getPanelLayout()
end
function getCursorPos()
- if df.global.cursor.x ~= -30000 then
- return copyall(df.global.cursor)
+ if g_cursor.x ~= -30000 then
+ return copyall(g_cursor)
end
end
@@ -56,6 +59,51 @@ function clearCursorPos()
df.global.cursor = xyz2pos(nil)
end
+function getSelection()
+ local p1, p2
+ if g_sel_rect.start_x ~= -30000 then
+ p1 = xyz2pos(g_sel_rect.start_x, g_sel_rect.start_y, g_sel_rect.start_z)
+ end
+ if g_sel_rect.end_x ~= -30000 then
+ p2 = xyz2pos(g_sel_rect.end_x, g_sel_rect.end_y, g_sel_rect.end_z)
+ end
+ return p1, p2
+end
+
+function setSelectionStart(pos)
+ g_sel_rect.start_x = pos.x
+ g_sel_rect.start_y = pos.y
+ g_sel_rect.start_z = pos.z
+end
+
+function setSelectionEnd(pos)
+ g_sel_rect.end_x = pos.x
+ g_sel_rect.end_y = pos.y
+ g_sel_rect.end_z = pos.z
+end
+
+function clearSelection()
+ g_sel_rect.start_x = -30000
+ g_sel_rect.start_y = -30000
+ g_sel_rect.start_z = -30000
+ g_sel_rect.end_x = -30000
+ g_sel_rect.end_y = -30000
+ g_sel_rect.end_z = -30000
+end
+
+function getSelectionRange(p1, p2)
+ local r1 = xyz2pos(
+ math.min(p1.x, p2.x), math.min(p1.y, p2.y), math.min(p1.z, p2.z)
+ )
+ local r2 = xyz2pos(
+ math.max(p1.x, p2.x), math.max(p1.y, p2.y), math.max(p1.z, p2.z)
+ )
+ local sz = xyz2pos(
+ r2.x - r1.x + 1, r2.y - r1.y + 1, r2.z - r1.z + 1
+ )
+ return r1, sz, r2
+end
+
Viewport = defclass(Viewport)
function Viewport.make(map,x,y,z)
@@ -88,6 +136,14 @@ function Viewport:set()
return vp
end
+function Viewport:getPos()
+ return xyz2pos(self.x1, self.y1, self.z)
+end
+
+function Viewport:getSize()
+ return xy2pos(self.width, self.height)
+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)),
@@ -111,6 +167,18 @@ function Viewport:isVisible(target,gap)
return self:isVisibleXY(target,gap) and target.z == self.z
end
+function Viewport:tileToScreen(coord)
+ return xyz2pos(coord.x - self.x1, coord.y - self.y1, coord.z - self.z)
+end
+
+function Viewport:getCenter()
+ return xyz2pos(
+ math.floor((self.x2+self.x1)/2),
+ math.floor((self.y2+self.y1)/2),
+ self.z
+ )
+end
+
function Viewport:centerOn(target)
return self:clip(
target.x - math.floor(self.width/2),
@@ -159,16 +227,24 @@ MOVEMENT_KEYS = {
CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 },
}
-function Viewport:scrollByKey(key)
+local function get_movement_delta(key, delta, big_step)
local info = MOVEMENT_KEYS[key]
if info then
- local delta = 10
- if info[4] then delta = 20 end
+ if info[4] then
+ delta = big_step
+ end
+ return delta*info[1], delta*info[2], info[3]
+ end
+end
+
+function Viewport:scrollByKey(key)
+ local dx, dy, dz = get_movement_delta(key, 10, 20)
+ if dx then
return self:clip(
- self.x1 + delta*info[1],
- self.y1 + delta*info[2],
- self.z + info[3]
+ self.x1 + dx,
+ self.y1 + dy,
+ self.z + dz
)
else
return self
@@ -189,16 +265,23 @@ function DwarfOverlay:getViewport(old_vp)
end
end
-function DwarfOverlay:moveCursorTo(cursor,viewport)
+function DwarfOverlay:moveCursorTo(cursor,viewport,gap)
setCursorPos(cursor)
- self:getViewport(viewport):reveal(cursor, 5, 0, 10):set()
+ self:zoomViewportTo(cursor,viewport,gap)
end
-function DwarfOverlay:selectBuilding(building,cursor,viewport)
+function DwarfOverlay:zoomViewportTo(target, viewport, gap)
+ if gap and self:getViewport():isVisible(target, gap) then
+ return
+ end
+ self:getViewport(viewport):reveal(target, 5, 0, 10):set()
+end
+
+function DwarfOverlay:selectBuilding(building,cursor,viewport,gap)
cursor = cursor or utils.getBuildingCenter(building)
df.global.world.selected_building = building
- self:moveCursorTo(cursor, viewport)
+ self:moveCursorTo(cursor, viewport, gap)
end
function DwarfOverlay:propagateMoveKeys(keys)
@@ -234,6 +317,31 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor)
end
end
+function DwarfOverlay:simulateCursorMovement(keys, anchor)
+ local layout = self.df_layout
+ local cursor = getCursorPos()
+ local cx, cy, cz = pos2xyz(cursor)
+
+ if anchor and keys.A_MOVE_SAME_SQUARE then
+ setCursorPos(anchor)
+ self:getViewport():centerOn(anchor):set()
+ return 'A_MOVE_SAME_SQUARE'
+ end
+
+ for code,_ in pairs(MOVEMENT_KEYS) do
+ if keys[code] then
+ local dx, dy, dz = get_movement_delta(code, 1, 10)
+ local ncur = xyz2pos(cx+dx, cy+dy, cz+dz)
+
+ if dfhack.maps.isValidTilePos(ncur) then
+ setCursorPos(ncur)
+ self:getViewport():reveal(ncur,4,10,6,true):set()
+ return code
+ end
+ end
+ end
+end
+
function DwarfOverlay:onAboutToShow(below)
local screen = dfhack.gui.getCurViewscreen()
if below then screen = below.parent end
diff --git a/library/lua/utils.lua b/library/lua/utils.lua
index 19a4e6f6..9fa473ed 100644
--- a/library/lua/utils.lua
+++ b/library/lua/utils.lua
@@ -381,6 +381,19 @@ function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z)
end
+function split_string(self, delimiter)
+ local result = { }
+ local from = 1
+ local delim_from, delim_to = string.find( self, delimiter, from )
+ while delim_from do
+ table.insert( result, string.sub( self, from , delim_from-1 ) )
+ from = delim_to + 1
+ delim_from, delim_to = string.find( self, delimiter, from )
+ end
+ table.insert( result, string.sub( self, from ) )
+ return result
+end
+
-- Ask a yes-no question
function prompt_yes_no(msg,default)
local prompt = msg
diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp
index d1aed897..9e78edd3 100644
--- a/library/modules/Buildings.cpp
+++ b/library/modules/Buildings.cpp
@@ -324,7 +324,7 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in
{
auto obj = (df::building_trapst*)bld;
if (obj->trap_type == trap_type::PressurePlate)
- obj->unk_cc = 500;
+ obj->ready_timeout = 500;
break;
}
default:
diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp
index 91a17e99..91df14ea 100644
--- a/library/modules/Gui.cpp
+++ b/library/modules/Gui.cpp
@@ -43,6 +43,7 @@ using namespace DFHack;
#include "modules/Job.h"
#include "modules/Screen.h"
+#include "modules/Maps.h"
#include "DataDefs.h"
#include "df/world.h"
@@ -81,6 +82,8 @@ using namespace DFHack;
#include "df/graphic.h"
#include "df/layer_object_listst.h"
#include "df/assign_trade_status.h"
+#include "df/announcement_flags.h"
+#include "df/announcements.h"
using namespace df::enums;
using df::global::gview;
@@ -88,6 +91,9 @@ using df::global::init;
using df::global::gps;
using df::global::ui;
using df::global::world;
+using df::global::selection_rect;
+using df::global::ui_menu_width;
+using df::global::ui_area_map_width;
static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx)
{
@@ -167,10 +173,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
else if (id == &df::building_trapst::_identity)
{
auto trap = (df::building_trapst*)selected;
- if (trap->trap_type == trap_type::Lever) {
- focus += "/Lever";
+ focus += "/" + enum_item_key(trap->trap_type);
+ if (trap->trap_type == trap_type::Lever)
jobs = true;
- }
}
else if (ui_building_in_assign && *ui_building_in_assign &&
ui_building_assign_type && ui_building_assign_units &&
@@ -183,6 +188,8 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += unit ? "/Unit" : "/None";
}
}
+ else
+ focus += "/" + enum_item_key(selected->getType());
if (jobs)
{
@@ -205,7 +212,14 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
if (ui_build_selector->building_type < 0)
focus += "/Type";
else if (ui_build_selector->stage != 2)
- focus += "/Position";
+ {
+ if (ui_build_selector->stage != 1)
+ focus += "/NoMaterials";
+ else
+ focus += "/Position";
+
+ focus += "/" + enum_item_key(ui_build_selector->building_type);
+ }
else
{
focus += "/Material";
@@ -921,8 +935,9 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet)
//
-void Gui::showAnnouncement(std::string message, int color, bool bright)
-{
+static void doShowAnnouncement(
+ df::announcement_type type, df::coord pos, std::string message, int color, bool bright
+) {
using df::global::world;
using df::global::cur_year;
using df::global::cur_year_tick;
@@ -948,6 +963,9 @@ void Gui::showAnnouncement(std::string message, int color, bool bright)
{
df::report *new_rep = new df::report();
+ new_rep->type = type;
+ new_rep->pos = pos;
+
new_rep->color = color;
new_rep->bright = bright;
new_rep->year = year;
@@ -969,7 +987,17 @@ void Gui::showAnnouncement(std::string message, int color, bool bright)
world->status.announcements.push_back(new_rep);
world->status.display_timer = 2000;
}
+}
+void Gui::showAnnouncement(std::string message, int color, bool bright)
+{
+ doShowAnnouncement(df::announcement_type(0), df::coord(), message, color, bright);
+}
+
+void Gui::showZoomAnnouncement(
+ df::announcement_type type, df::coord pos, std::string message, int color, bool bright
+) {
+ doShowAnnouncement(type, pos, message, color, bright);
}
void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
@@ -983,6 +1011,29 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
world->status.popups.push_back(popup);
}
+void Gui::showAutoAnnouncement(
+ df::announcement_type type, df::coord pos, std::string message, int color, bool bright
+) {
+ using df::global::announcements;
+
+ df::announcement_flags flags;
+ if (is_valid_enum_item(type) && announcements)
+ flags = announcements->flags[type];
+
+ doShowAnnouncement(type, pos, message, color, bright);
+
+ if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER)
+ {
+ resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE);
+
+ if (flags.bits.RECENTER && pos.isValid())
+ revealInDwarfmodeMap(pos, true);
+ }
+
+ if (flags.bits.DO_MEGA)
+ showPopupAnnouncement(message, color, bright);
+}
+
df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
{
df::viewscreen * ws = &gview->view;
@@ -998,6 +1049,127 @@ df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
return ws;
}
+df::coord Gui::getViewportPos()
+{
+ if (!df::global::window_x || !df::global::window_y || !df::global::window_z)
+ return df::coord(0,0,0);
+
+ return df::coord(*df::global::window_x, *df::global::window_y, *df::global::window_z);
+}
+
+df::coord Gui::getCursorPos()
+{
+ using df::global::cursor;
+ if (!cursor)
+ return df::coord();
+
+ return df::coord(cursor->x, cursor->y, cursor->z);
+}
+
+Gui::DwarfmodeDims Gui::getDwarfmodeViewDims()
+{
+ DwarfmodeDims dims;
+
+ auto ws = Screen::getWindowSize();
+ dims.y1 = 1;
+ dims.y2 = ws.y-2;
+ dims.map_x1 = 1;
+ dims.map_x2 = ws.x-2;
+ dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1;
+ dims.menu_forced = false;
+
+ int menu_pos = (ui_menu_width ? *ui_menu_width : 2);
+ int area_pos = (ui_area_map_width ? *ui_area_map_width : 3);
+
+ if (ui && ui->main.mode && menu_pos >= area_pos)
+ {
+ dims.menu_forced = true;
+ menu_pos = area_pos-1;
+ }
+
+ dims.area_on = (area_pos < 3);
+ dims.menu_on = (menu_pos < area_pos);
+
+ if (dims.menu_on)
+ {
+ dims.menu_x2 = ws.x - 2;
+ dims.menu_x1 = dims.menu_x2 - Gui::MENU_WIDTH + 1;
+ if (menu_pos == 1)
+ dims.menu_x1 -= Gui::AREA_MAP_WIDTH + 1;
+ dims.map_x2 = dims.menu_x1 - 2;
+ }
+ if (dims.area_on)
+ {
+ dims.area_x2 = ws.x-2;
+ dims.area_x1 = dims.area_x2 - Gui::AREA_MAP_WIDTH + 1;
+ if (dims.menu_on)
+ dims.menu_x2 = dims.area_x1 - 2;
+ else
+ dims.map_x2 = dims.area_x1 - 2;
+ }
+
+ return dims;
+}
+
+void Gui::resetDwarfmodeView(bool pause)
+{
+ using df::global::cursor;
+
+ if (ui)
+ {
+ ui->follow_unit = -1;
+ ui->follow_item = -1;
+ ui->main.mode = ui_sidebar_mode::Default;
+ }
+
+ if (selection_rect)
+ {
+ selection_rect->start_x = -30000;
+ selection_rect->end_x = -30000;
+ }
+
+ if (cursor)
+ cursor->x = cursor->y = cursor->z = -30000;
+
+ if (pause && df::global::pause_state)
+ *df::global::pause_state = true;
+}
+
+bool Gui::revealInDwarfmodeMap(df::coord pos, bool center)
+{
+ using df::global::window_x;
+ using df::global::window_y;
+ using df::global::window_z;
+
+ if (!window_x || !window_y || !window_z || !world)
+ return false;
+ if (!Maps::isValidTilePos(pos))
+ return false;
+
+ auto dims = getDwarfmodeViewDims();
+ int w = dims.map_x2 - dims.map_x1 + 1;
+ int h = dims.y2 - dims.y1 + 1;
+
+ *window_z = pos.z;
+
+ if (center)
+ {
+ *window_x = pos.x - w/2;
+ *window_y = pos.y - h/2;
+ }
+ else
+ {
+ while (*window_x + w < pos.x+5) *window_x += 10;
+ while (*window_y + h < pos.y+5) *window_y += 10;
+ while (*window_x + 5 > pos.x) *window_x -= 10;
+ while (*window_y + 5 > pos.y) *window_y -= 10;
+ }
+
+ *window_x = std::max(0, std::min(*window_x, world->map.x_count-w));
+ *window_y = std::max(0, std::min(*window_y, world->map.y_count-h));
+ return true;
+}
+
bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z)
{
x = *df::global::window_x;
diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp
index 3ab156d7..d0401164 100644
--- a/library/modules/Maps.cpp
+++ b/library/modules/Maps.cpp
@@ -57,6 +57,8 @@ using namespace std;
#include "df/builtin_mats.h"
#include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h"
+#include "df/region_map_entry.h"
+#include "df/flow_info.h"
using namespace DFHack;
using namespace df::enums;
@@ -137,17 +139,57 @@ df::map_block *Maps::getBlock (int32_t blockx, int32_t blocky, int32_t blockz)
return world->map.block_index[blockx][blocky][blockz];
}
-df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
+bool Maps::isValidTilePos(int32_t x, int32_t y, int32_t z)
{
if (!IsValid())
- return NULL;
+ return false;
if ((x < 0) || (y < 0) || (z < 0))
- return NULL;
+ return false;
if ((x >= world->map.x_count) || (y >= world->map.y_count) || (z >= world->map.z_count))
+ return false;
+ return true;
+}
+
+df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
+{
+ if (!isValidTilePos(x,y,z))
return NULL;
return world->map.block_index[x >> 4][y >> 4][z];
}
+df::map_block *Maps::ensureTileBlock (int32_t x, int32_t y, int32_t z)
+{
+ if (!isValidTilePos(x,y,z))
+ return NULL;
+
+ auto column = world->map.block_index[x >> 4][y >> 4];
+ auto &slot = column[z];
+ if (slot)
+ return slot;
+
+ // Find another block below
+ int z2 = z;
+ while (z2 >= 0 && !column[z2]) z2--;
+ if (z2 < 0)
+ return NULL;
+
+ slot = new df::map_block();
+ slot->region_pos = column[z2]->region_pos;
+ slot->map_pos = column[z2]->map_pos;
+ slot->map_pos.z = z;
+
+ // Assume sky
+ df::tile_designation dsgn(0);
+ dsgn.bits.light = true;
+ dsgn.bits.outside = true;
+
+ for (int tx = 0; tx < 16; tx++)
+ for (int ty = 0; ty < 16; ty++)
+ slot->designation[tx][ty] = dsgn;
+
+ return slot;
+}
+
df::tiletype *Maps::getTileType(int32_t x, int32_t y, int32_t z)
{
df::map_block *block = getTileBlock(x,y,z);
@@ -166,7 +208,7 @@ df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z)
return block ? &block->occupancy[x&15][y&15] : NULL;
}
-df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos)
+df::region_map_entry *Maps::getRegionBiome(df::coord2d rgn_pos)
{
auto data = world->world_data;
if (!data)
@@ -203,6 +245,26 @@ void Maps::enableBlockUpdates(df::map_block *blk, bool flow, bool temperature)
}
}
+df::flow_info *Maps::spawnFlow(df::coord pos, df::flow_type type, int mat_type, int mat_index, int density)
+{
+ using df::global::flows;
+
+ auto block = getTileBlock(pos);
+ if (!flows || !block)
+ return NULL;
+
+ auto flow = new df::flow_info();
+ flow->type = type;
+ flow->mat_type = mat_type;
+ flow->mat_index = mat_index;
+ flow->density = std::min(100, density);
+ flow->pos = pos;
+
+ block->flows.push_back(flow);
+ flows->push_back(flow);
+ return flow;
+}
+
df::feature_init *Maps::getGlobalInitFeature(int32_t index)
{
auto data = world->world_data;
@@ -484,8 +546,14 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
valid = false;
bcoord = _bcoord;
block = Maps::getBlock(bcoord);
- item_counts = NULL;
tags = NULL;
+
+ init();
+}
+
+void MapExtras::Block::init()
+{
+ item_counts = NULL;
tiles = NULL;
basemats = NULL;
@@ -508,6 +576,23 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
}
}
+bool MapExtras::Block::Allocate()
+{
+ if (block)
+ return true;
+
+ block = Maps::ensureTileBlock(bcoord.x*16, bcoord.y*16, bcoord.z);
+ if (!block)
+ return false;
+
+ delete item_counts;
+ delete tiles;
+ delete basemats;
+ init();
+
+ return true;
+}
+
MapExtras::Block::~Block()
{
delete[] item_counts;
diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp
index c2377f2c..9f258fe0 100644
--- a/library/modules/Screen.cpp
+++ b/library/modules/Screen.cpp
@@ -100,7 +100,7 @@ static void doSetTile(const Pen &pen, int index)
bool Screen::paintTile(const Pen &pen, int x, int y)
{
- if (!gps) return false;
+ if (!gps || !pen.valid()) return false;
int dimx = gps->dimx, dimy = gps->dimy;
if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false;
@@ -109,6 +109,41 @@ bool Screen::paintTile(const Pen &pen, int x, int y)
return true;
}
+Pen Screen::readTile(int x, int y)
+{
+ if (!gps) return Pen(0,0,0,-1);
+
+ int dimx = gps->dimx, dimy = gps->dimy;
+ if (x < 0 || x >= dimx || y < 0 || y >= dimy)
+ return Pen(0,0,0,-1);
+
+ int index = x*dimy + y;
+ auto screen = gps->screen + index*4;
+ if (screen[3] & 0x80)
+ return Pen(0,0,0,-1);
+
+ Pen pen(
+ screen[0], screen[1], screen[2], screen[3]?true:false,
+ gps->screentexpos[index]
+ );
+
+ if (pen.tile)
+ {
+ if (gps->screentexpos_grayscale[index])
+ {
+ pen.tile_mode = Screen::Pen::TileColor;
+ pen.tile_fg = gps->screentexpos_cf[index];
+ pen.tile_bg = gps->screentexpos_cbr[index];
+ }
+ else if (gps->screentexpos_addcolor[index])
+ {
+ pen.tile_mode = Screen::Pen::CharColor;
+ }
+ }
+
+ return pen;
+}
+
bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
{
if (!gps || y < 0 || y >= gps->dimy) return false;
@@ -132,7 +167,7 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2)
{
- if (!gps) return false;
+ if (!gps || !pen.valid()) return false;
if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0;
diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp
index 282157a3..01b7b50f 100644
--- a/library/modules/Units.cpp
+++ b/library/modules/Units.cpp
@@ -63,11 +63,15 @@ using namespace std;
#include "df/burrow.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
+#include "df/game_mode.h"
+#include "df/unit_misc_trait.h"
+#include "df/unit_skill.h"
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::ui;
+using df::global::gamemode;
bool Units::isValid()
{
@@ -613,6 +617,58 @@ df::nemesis_record *Units::getNemesis(df::unit *unit)
return NULL;
}
+
+bool Units::isHidingCurse(df::unit *unit)
+{
+ if (!unit->job.hunt_target)
+ {
+ auto identity = Units::getIdentity(unit);
+ if (identity && identity->unk_4c == 0)
+ return true;
+ }
+
+ return false;
+}
+
+int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr)
+{
+ auto &aobj = unit->body.physical_attrs[attr];
+ int value = std::max(0, aobj.value - aobj.soft_demotion);
+
+ if (auto mod = unit->curse.attr_change)
+ {
+ int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr];
+
+ if (isHidingCurse(unit))
+ value = std::min(value, mvalue);
+ else
+ value = mvalue;
+ }
+
+ return std::max(0, value);
+}
+
+int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr)
+{
+ auto soul = unit->status.current_soul;
+ if (!soul) return 0;
+
+ auto &aobj = soul->mental_attrs[attr];
+ int value = std::max(0, aobj.value - aobj.soft_demotion);
+
+ if (auto mod = unit->curse.attr_change)
+ {
+ int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr];
+
+ if (isHidingCurse(unit))
+ value = std::min(value, mvalue);
+ else
+ value = mvalue;
+ }
+
+ return std::max(0, value);
+}
+
static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
{
auto creature = df::creature_raw::find(race);
@@ -626,8 +682,9 @@ static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
return craw->flags.is_set(flag);
}
-static bool isCrazed(df::unit *unit)
+bool Units::isCrazed(df::unit *unit)
{
+ CHECK_NULL_POINTER(unit);
if (unit->flags3.bits.scuttle)
return false;
if (unit->curse.rem_tags1.bits.CRAZED)
@@ -637,13 +694,64 @@ static bool isCrazed(df::unit *unit)
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED);
}
-static bool isOpposedToLife(df::unit *unit)
+bool Units::isOpposedToLife(df::unit *unit)
{
+ CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE)
return false;
if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE)
return true;
- return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CANNOT_UNDEAD);
+ return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE);
+}
+
+bool Units::hasExtravision(df::unit *unit)
+{
+ CHECK_NULL_POINTER(unit);
+ if (unit->curse.rem_tags1.bits.EXTRAVISION)
+ return false;
+ if (unit->curse.add_tags1.bits.EXTRAVISION)
+ return true;
+ return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION);
+}
+
+bool Units::isBloodsucker(df::unit *unit)
+{
+ CHECK_NULL_POINTER(unit);
+ if (unit->curse.rem_tags1.bits.BLOODSUCKER)
+ return false;
+ if (unit->curse.add_tags1.bits.BLOODSUCKER)
+ return true;
+ return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER);
+}
+
+bool Units::isMischievous(df::unit *unit)
+{
+ CHECK_NULL_POINTER(unit);
+ if (unit->curse.rem_tags1.bits.MISCHIEVOUS)
+ return false;
+ if (unit->curse.add_tags1.bits.MISCHIEVOUS)
+ return true;
+ return casteFlagSet(unit->race, unit->caste, caste_raw_flags::MISCHIEVOUS);
+}
+
+df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create)
+{
+ CHECK_NULL_POINTER(unit);
+
+ auto &vec = unit->status.misc_traits;
+ for (size_t i = 0; i < vec.size(); i++)
+ if (vec[i]->id == type)
+ return vec[i];
+
+ if (create)
+ {
+ auto obj = new df::unit_misc_trait();
+ obj->id = type;
+ vec.push_back(obj);
+ return obj;
+ }
+
+ return NULL;
}
bool DFHack::Units::isDead(df::unit *unit)
@@ -753,6 +861,371 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age)
return cur_time - birth_time;
}
+inline void adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2)
+{
+ if (is_adventure)
+ {
+ if (value >= adv1_2) rating >>= 1;
+ else if (value >= adv3_4) rating = rating*3/4;
+ else if (value >= adv9_10) rating = rating*9/10;
+ }
+ else
+ {
+ if (value >= dwarf1_2) rating >>= 1;
+ else if (value >= dwarf3_4) rating = rating*3/4;
+ }
+}
+
+int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id)
+{
+ CHECK_NULL_POINTER(unit);
+
+ /*
+ * This is 100% reverse-engineered from DF code.
+ */
+
+ if (!unit->status.current_soul)
+ return 0;
+
+ // Retrieve skill from unit soul:
+
+ df::enum_field<df::job_skill,int16_t> key(skill_id);
+ auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key);
+
+ int rating = 0;
+ if (skill)
+ rating = std::max(0, int(skill->rating) - skill->rusty);
+
+ // Apply special states
+
+ if (unit->counters.soldier_mood == df::unit::T_counters::None)
+ {
+ if (unit->counters.nausea > 0) rating >>= 1;
+ if (unit->counters.winded > 0) rating >>= 1;
+ if (unit->counters.stunned > 0) rating >>= 1;
+ if (unit->counters.dizziness > 0) rating >>= 1;
+ if (unit->counters2.fever > 0) rating >>= 1;
+ }
+
+ if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
+ {
+ if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle &&
+ !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged &&
+ !hasExtravision(unit))
+ {
+ rating >>= 2;
+ }
+ if (unit->counters.pain >= 100 && unit->mood == -1)
+ {
+ rating >>= 1;
+ }
+ if (unit->counters2.exhaustion >= 2000)
+ {
+ rating = rating*3/4;
+ if (unit->counters2.exhaustion >= 4000)
+ {
+ rating = rating*3/4;
+ if (unit->counters2.exhaustion >= 6000)
+ rating = rating*3/4;
+ }
+ }
+ }
+
+ // Hunger etc timers
+
+ bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE);
+
+ if (!unit->flags3.bits.scuttle && isBloodsucker(unit))
+ {
+ using namespace df::enums::misc_trait_type;
+
+ if (auto trait = getMiscTrait(unit, TimeSinceSuckedBlood))
+ {
+ adjust_skill_rating(
+ rating, is_adventure, trait->value,
+ 302400, 403200, // dwf 3/4; 1/2
+ 1209600, 1209600, 2419200 // adv 9/10; 3/4; 1/2
+ );
+ }
+ }
+
+ adjust_skill_rating(
+ rating, is_adventure, unit->counters2.thirst_timer,
+ 50000, 50000, 115200, 172800, 345600
+ );
+ adjust_skill_rating(
+ rating, is_adventure, unit->counters2.hunger_timer,
+ 75000, 75000, 172800, 1209600, 2592000
+ );
+ if (is_adventure && unit->counters2.sleepiness_timer >= 846000)
+ rating >>= 2;
+ else
+ adjust_skill_rating(
+ rating, is_adventure, unit->counters2.sleepiness_timer,
+ 150000, 150000, 172800, 259200, 345600
+ );
+
+ return rating;
+}
+
+inline void adjust_speed_rating(int &rating, bool is_adventure, int value, int dwarf100, int dwarf200, int adv50, int adv75, int adv100, int adv200)
+{
+ if (is_adventure)
+ {
+ if (value >= adv200) rating += 200;
+ else if (value >= adv100) rating += 100;
+ else if (value >= adv75) rating += 75;
+ else if (value >= adv50) rating += 50;
+ }
+ else
+ {
+ if (value >= dwarf200) rating += 200;
+ else if (value >= dwarf100) rating += 100;
+ }
+}
+
+static int calcInventoryWeight(df::unit *unit)
+{
+ int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR);
+ int armor_mul = 15 - std::min(15, armor_skill);
+
+ int inv_weight = 0, inv_weight_fraction = 0;
+
+ for (size_t i = 0; i < unit->inventory.size(); i++)
+ {
+ auto item = unit->inventory[i]->item;
+ if (!item->flags.bits.weight_computed)
+ continue;
+
+ int wval = item->weight;
+ int wfval = item->weight_fraction;
+ auto mode = unit->inventory[i]->mode;
+
+ if ((mode == df::unit_inventory_item::Worn ||
+ mode == df::unit_inventory_item::WrappedAround) &&
+ item->isArmor() && armor_skill > 1)
+ {
+ wval = wval * armor_mul / 16;
+ wfval = wfval * armor_mul / 16;
+ }
+
+ inv_weight += wval;
+ inv_weight_fraction += wfval;
+ }
+
+ return inv_weight*100 + inv_weight_fraction/10000;
+}
+
+int Units::computeMovementSpeed(df::unit *unit)
+{
+ using namespace df::enums::physical_attribute_type;
+
+ /*
+ * Pure reverse-engineered computation of unit _slowness_,
+ * i.e. number of ticks to move * 100.
+ */
+
+ // Base speed
+
+ auto creature = df::creature_raw::find(unit->race);
+ if (!creature)
+ return 0;
+
+ auto craw = vector_get(creature->caste, unit->caste);
+ if (!craw)
+ return 0;
+
+ int speed = craw->misc.speed;
+
+ if (unit->flags3.bits.ghostly)
+ return speed;
+
+ // Curse multiplier
+
+ if (unit->curse.speed_mul_percent != 100)
+ {
+ speed *= 100;
+ if (unit->curse.speed_mul_percent != 0)
+ speed /= unit->curse.speed_mul_percent;
+ }
+
+ speed += unit->curse.speed_add;
+
+ // Swimming
+
+ auto cur_liquid = unit->status2.liquid_type.bits.liquid_type;
+ bool in_magma = (cur_liquid == tile_liquid::Magma);
+
+ if (unit->flags2.bits.swimming)
+ {
+ speed = craw->misc.swim_speed;
+ if (in_magma)
+ speed *= 2;
+
+ if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED))
+ {
+ int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING);
+
+ // Originally a switch:
+ if (skill > 1)
+ speed = speed * std::max(6, 21-skill) / 20;
+ }
+ }
+ else
+ {
+ int delta = 150*unit->status2.liquid_depth;
+ if (in_magma)
+ delta *= 2;
+ speed += delta;
+ }
+
+ // General counters and flags
+
+ if (unit->profession == profession::BABY)
+ speed += 3000;
+
+ if (unit->flags3.bits.unk15)
+ speed /= 20;
+
+ if (unit->counters2.exhaustion >= 2000)
+ {
+ speed += 200;
+ if (unit->counters2.exhaustion >= 4000)
+ {
+ speed += 200;
+ if (unit->counters2.exhaustion >= 6000)
+ speed += 200;
+ }
+ }
+
+ if (unit->flags2.bits.gutted) speed += 2000;
+
+ if (unit->counters.soldier_mood == df::unit::T_counters::None)
+ {
+ if (unit->counters.nausea > 0) speed += 1000;
+ if (unit->counters.winded > 0) speed += 1000;
+ if (unit->counters.stunned > 0) speed += 1000;
+ if (unit->counters.dizziness > 0) speed += 1000;
+ if (unit->counters2.fever > 0) speed += 1000;
+ }
+
+ if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
+ {
+ if (unit->counters.pain >= 100 && unit->mood == -1)
+ speed += 1000;
+ }
+
+ // Hunger etc timers
+
+ bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE);
+
+ if (!unit->flags3.bits.scuttle && Units::isBloodsucker(unit))
+ {
+ using namespace df::enums::misc_trait_type;
+
+ if (auto trait = Units::getMiscTrait(unit, TimeSinceSuckedBlood))
+ {
+ adjust_speed_rating(
+ speed, is_adventure, trait->value,
+ 302400, 403200, // dwf 100; 200
+ 1209600, 1209600, 1209600, 2419200 // adv 50; 75; 100; 200
+ );
+ }
+ }
+
+ adjust_speed_rating(
+ speed, is_adventure, unit->counters2.thirst_timer,
+ 50000, 0x7fffffff, 172800, 172800, 172800, 345600
+ );
+ adjust_speed_rating(
+ speed, is_adventure, unit->counters2.hunger_timer,
+ 75000, 0x7fffffff, 1209600, 1209600, 1209600, 2592000
+ );
+ adjust_speed_rating(
+ speed, is_adventure, unit->counters2.sleepiness_timer,
+ 57600, 150000, 172800, 259200, 345600, 864000
+ );
+
+ // Activity state
+
+ if (unit->relations.draggee_id != -1) speed += 1000;
+
+ if (unit->flags1.bits.on_ground)
+ speed += 2000;
+ else if (unit->flags3.bits.on_crutch)
+ {
+ int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK);
+ speed += 2000 - 100*std::min(20, skill);
+ }
+
+ if (unit->flags1.bits.hidden_in_ambush && !Units::isMischievous(unit))
+ {
+ int skill = Units::getEffectiveSkill(unit, job_skill::SNEAK);
+ speed += 2000 - 100*std::min(20, skill);
+ }
+
+ if (unsigned(unit->counters2.paralysis-1) <= 98)
+ speed += unit->counters2.paralysis*10;
+ if (unsigned(unit->counters.webbed-1) <= 8)
+ speed += unit->counters.webbed*100;
+
+ // Muscle weight vs vascular tissue (?)
+
+ auto &attr_tissue = unit->body.physical_attr_tissues;
+ int muscle = attr_tissue[STRENGTH];
+ int blood = attr_tissue[AGILITY];
+ speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*muscle/blood)));
+
+ // Attributes
+
+ int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH);
+ int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY);
+
+ int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr));
+ speed = ((total_attr-200)*(speed/2) + (3800-total_attr)*(speed*3/2))/3600;
+
+ // Stance
+
+ if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2)
+ {
+ // WTF
+ int as = unit->status2.able_stand;
+ int x = (as-1) - (as>>1);
+ int y = as - unit->status2.able_stand_impair;
+ if (unit->flags3.bits.on_crutch) y--;
+ y = y * 500 / x;
+ if (y > 0) speed += y;
+ }
+
+ // Mood
+
+ if (unit->mood == mood_type::Melancholy) speed += 8000;
+
+ // Inventory encumberance
+
+ int total_weight = calcInventoryWeight(unit);
+ int free_weight = std::max(1, muscle/10 + strength_attr*3);
+
+ if (free_weight < total_weight)
+ {
+ int delta = (total_weight - free_weight)/10 + 1;
+ if (!is_adventure)
+ delta = std::min(5000, delta);
+ speed += delta;
+ }
+
+ // skipped: unknown loop on inventory items that amounts to 0 change
+
+ if (is_adventure)
+ {
+ auto player = vector_get(world->units.active, 0);
+ if (player && player->id == unit->relations.group_leader_id)
+ speed = std::min(speed, computeMovementSpeed(player));
+ }
+
+ return std::min(10000, std::max(0, speed));
+}
+
static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b)
{
if (a.position->precedence < b.position->precedence)
diff --git a/library/modules/World.cpp b/library/modules/World.cpp
index 393e7cbf..e14aa02a 100644
--- a/library/modules/World.cpp
+++ b/library/modules/World.cpp
@@ -285,13 +285,13 @@ PersistentDataItem World::GetPersistentData(int entry_id)
PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
{
- *added = false;
+ if (added) *added = false;
PersistentDataItem rv = GetPersistentData(key);
if (!rv.isValid())
{
- *added = true;
+ if (added) *added = true;
rv = AddPersistentData(key);
}
diff --git a/library/xml b/library/xml
-Subproject abcb667bc832048552d8cbc8f4830936f8b6339
+Subproject d55f1cf43dd71d3abee724bfa88a0a401b4ccaa