Merge branch 'master' of git://github.com/peterix/dfhack

develop
Robert Heinrich 2012-04-21 11:52:25 +02:00
commit a282c9dede
36 changed files with 3554 additions and 1196 deletions

@ -63,7 +63,7 @@ set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "07") set(DF_VERSION_PATCH "07")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.") SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")

@ -209,7 +209,7 @@ Implemented features:
* ``ref:insert(index,item)`` * ``ref:insert(index,item)``
Inserts a new item at the specified index. To add at the end, Inserts a new item at the specified index. To add at the end,
use ``#ref`` as index. use ``#ref``, or just ``'#'`` as index.
* ``ref:erase(index)`` * ``ref:erase(index)``
@ -428,6 +428,11 @@ Currently it defines the following features:
If the thread owns the interactive console, shows a prompt If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns *nil, error*. and returns the entered string. Otherwise returns *nil, error*.
Depending on the context, this function may actually yield the
running coroutine and let the C++ code release the core suspend
lock. Using an explicit ``dfhack.with_suspend`` will prevent
this, forcing the function to block on input with lock held.
* ``dfhack.interpreter([prompt[,env[,history_filename]]])`` * ``dfhack.interpreter([prompt[,env[,history_filename]]])``
Starts an interactive lua interpreter, using the specified prompt Starts an interactive lua interpreter, using the specified prompt
@ -449,6 +454,10 @@ Currently it defines the following features:
Just like pcall, but also prints the error using printerr before Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function. returning. Intended as a convenience function.
* ``dfhack.saferesume(coroutine[,args...])``
Compares to coroutine.resume like dfhack.safecall vs pcall.
* ``dfhack.with_suspend(f[,args...])`` * ``dfhack.with_suspend(f[,args...])``
Calls ``f`` with arguments after grabbing the DF core suspend lock. Calls ``f`` with arguments after grabbing the DF core suspend lock.
@ -799,3 +808,51 @@ Maps module
* ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)`` * ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)``
Adds or removes the tile from the burrow. Returns *false* if invalid coords. Adds or removes the tile from the burrow. Returns *false* if invalid coords.
Core interpreter context
========================
While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.
Core context specific functions:
* ``dfhack.is_core_context``
Boolean value; *true* in the core context.
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
Event type
----------
An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.
This type itself is available in any context, but only the
core context has the actual events defined by C++ code.
Features:
* ``dfhack.event.new()``
Creates a new instance of an event.
* ``event[key] = function``
Sets the function as one of the listeners.
**NOTE**: The ``df.NULL`` key is reserved for the use by
the C++ owner of the event, and has some special semantics.
* ``event(args...)``
Invokes all listeners contained in the event in an arbitrary
order using ``dfhack.safecall``.

@ -344,6 +344,10 @@ ul.auto-toc {
<li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li> <li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#core-interpreter-context" id="id19">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id20">Event type</a></li>
</ul>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -512,7 +516,7 @@ possible.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">ref:insert(index,item)</tt></p> <li><p class="first"><tt class="docutils literal">ref:insert(index,item)</tt></p>
<p>Inserts a new item at the specified index. To add at the end, <p>Inserts a new item at the specified index. To add at the end,
use <tt class="docutils literal">#ref</tt> as index.</p> use <tt class="docutils literal">#ref</tt>, or just <tt class="docutils literal">'#'</tt> as index.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">ref:erase(index)</tt></p> <li><p class="first"><tt class="docutils literal">ref:erase(index)</tt></p>
<p>Removes the element at the given valid index.</p> <p>Removes the element at the given valid index.</p>
@ -704,6 +708,10 @@ works with DFHack output infrastructure.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.lineedit([prompt[,history_filename]])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.lineedit([prompt[,history_filename]])</span></tt></p>
<p>If the thread owns the interactive console, shows a prompt <p>If the thread owns the interactive console, shows a prompt
and returns the entered string. Otherwise returns <em>nil, error</em>.</p> and returns the entered string. Otherwise returns <em>nil, error</em>.</p>
<p>Depending on the context, this function may actually yield the
running coroutine and let the C++ code release the core suspend
lock. Using an explicit <tt class="docutils literal">dfhack.with_suspend</tt> will prevent
this, forcing the function to block on input with lock held.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,env[,history_filename]]])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.interpreter([prompt[,env[,history_filename]]])</span></tt></p>
<p>Starts an interactive lua interpreter, using the specified prompt <p>Starts an interactive lua interpreter, using the specified prompt
@ -721,6 +729,9 @@ in C++, and dfhack.safecall.</p>
<p>Just like pcall, but also prints the error using printerr before <p>Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.</p> returning. Intended as a convenience function.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.saferesume(coroutine[,args...])</span></tt></p>
<p>Compares to coroutine.resume like dfhack.safecall vs pcall.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_suspend(f[,args...])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_suspend(f[,args...])</span></tt></p>
<p>Calls <tt class="docutils literal">f</tt> with arguments after grabbing the DF core suspend lock. <p>Calls <tt class="docutils literal">f</tt> with arguments after grabbing the DF core suspend lock.
Suspending is necessary for accessing a consistent state of DF memory.</p> Suspending is necessary for accessing a consistent state of DF memory.</p>
@ -1010,6 +1021,45 @@ Returns <em>false</em> in case of error.</p>
</ul> </ul>
</div> </div>
</div> </div>
<div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id19">Core interpreter context</a></h2>
<p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p>
<p>Core context specific functions:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.is_core_context</tt></p>
<p>Boolean value; <em>true</em> in the core context.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.onStateChange.foo = function(code)</tt></p>
<p>Event. Receives the same codes as plugin_onstatechange in C++.</p>
</li>
</ul>
<div class="section" id="event-type">
<h3><a class="toc-backref" href="#id20">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.</p>
<p>This type itself is available in any context, but only the
core context has the actual events defined by C++ code.</p>
<p>Features:</p>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.event.new()</tt></p>
<p>Creates a new instance of an event.</p>
</li>
<li><p class="first"><tt class="docutils literal">event[key] = function</tt></p>
<p>Sets the function as one of the listeners.</p>
<p><strong>NOTE</strong>: The <tt class="docutils literal">df.NULL</tt> key is reserved for the use by
the C++ owner of the event, and has some special semantics.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">event(args...)</span></tt></p>
<p>Invokes all listeners contained in the event in an arbitrary
order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</li>
</ul>
</div>
</div>
</div> </div>
</div> </div>
</body> </body>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -4,5 +4,6 @@ IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
mkdir VC2010 mkdir VC2010
cd VC2010 cd VC2010
echo generating a build folder echo generating a build folder
for /f "delims=" %%a in ('DATE /T') do @set myvar=breakfast-%%a rem for /f "delims=" %%a in ('DATE /T') do @set myvar=breakfast-%BUILD_NUMBER%
set myvar=breakfast-%BUILD_NUMBER%
cmake ..\.. -G"Visual Studio 10" -DDFHACK_RELEASE="%myvar%" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1 cmake ..\.. -G"Visual Studio 10" -DDFHACK_RELEASE="%myvar%" -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_DF2MC=1 -DBUILD_DFUSION=1 -DBUILD_STONESENSE=1 -DBUILD_SERVER=1

@ -1,4 +1,6 @@
@echo off
call "%VS100COMNTOOLS%vsvars32.bat" call "%VS100COMNTOOLS%vsvars32.bat"
cd VC2010 cd VC2010
msbuild /m /p:Platform=Win32 /p:Configuration=RelWithDebInfo PACKAGE.vcxproj msbuild /m /p:Platform=Win32 /p:Configuration=RelWithDebInfo PACKAGE.vcxproj
cd .. cd ..
exit %ERRORLEVEL%

@ -1,4 +1,6 @@
@echo off
call "%VS100COMNTOOLS%vsvars32.bat" call "%VS100COMNTOOLS%vsvars32.bat"
cd VC2010 cd VC2010
msbuild /m /p:Platform=Win32 /p:Configuration=Release PACKAGE.vcxproj msbuild /m /p:Platform=Win32 /p:Configuration=Release PACKAGE.vcxproj
cd .. cd ..
exit %ERRORLEVEL%

@ -1,7 +1,7 @@
PROJECT ( lua CXX ) PROJECT ( lua CXX )
CMAKE_MINIMUM_REQUIRED(VERSION 2.8) CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DLUA_USE_APICHECK") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_APICHECK")
IF(WIN32) IF(WIN32)
ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE )

@ -403,7 +403,7 @@ static void finishCcall (lua_State *L) {
lua_assert(ci->u.c.k != NULL); /* must have a continuation */ lua_assert(ci->u.c.k != NULL); /* must have a continuation */
lua_assert(L->nny == 0); lua_assert(L->nny == 0);
/* finish 'luaD_call' */ /* finish 'luaD_call' */
L->nCcalls--; //L->nCcalls--;
/* finish 'lua_callk' */ /* finish 'lua_callk' */
adjustresults(L, ci->nresults); adjustresults(L, ci->nresults);
/* call continuation function */ /* call continuation function */
@ -513,7 +513,7 @@ static void resume (lua_State *L, void *ud) {
api_checknelems(L, n); api_checknelems(L, n);
firstArg = L->top - n; /* yield results come from continuation */ firstArg = L->top - n; /* yield results come from continuation */
} }
L->nCcalls--; /* finish 'luaD_call' */ //L->nCcalls--; /* finish 'luaD_call' */
luaD_poscall(L, firstArg); /* finish 'luaD_precall' */ luaD_poscall(L, firstArg); /* finish 'luaD_precall' */
} }
unroll(L, NULL); unroll(L, NULL);

@ -223,12 +223,13 @@ namespace DFHack
size_t plen = prompt.size(); size_t plen = prompt.size();
const char * buf = raw_buffer.c_str(); const char * buf = raw_buffer.c_str();
size_t len = raw_buffer.size(); size_t len = raw_buffer.size();
int cooked_cursor = raw_cursor;
while ((plen + raw_cursor) >= cols) while ((plen + cooked_cursor) >= cols)
{ {
buf++; buf++;
len--; len--;
raw_cursor--; cooked_cursor--;
} }
while (plen + len > cols) while (plen + len > cols)
{ {
@ -247,7 +248,7 @@ namespace DFHack
output(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y); output(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y);
free(tmp); free(tmp);
} }
inf.dwCursorPosition.X = (SHORT)(raw_cursor + plen); inf.dwCursorPosition.X = (SHORT)(cooked_cursor + plen);
SetConsoleCursorPosition(console_out, inf.dwCursorPosition); SetConsoleCursorPosition(console_out, inf.dwCursorPosition);
} }

@ -49,6 +49,8 @@ using namespace std;
#include "modules/Graphic.h" #include "modules/Graphic.h"
#include "modules/Windows.h" #include "modules/Windows.h"
#include "RemoteServer.h" #include "RemoteServer.h"
#include "LuaTools.h"
using namespace DFHack; using namespace DFHack;
#include "df/ui.h" #include "df/ui.h"
@ -118,7 +120,7 @@ struct Core::Private
} }
}; };
void cheap_tokenise(string const& input, vector<string> &output) void Core::cheap_tokenise(string const& input, vector<string> &output)
{ {
string *cur = NULL; string *cur = NULL;
@ -177,7 +179,7 @@ void fHKthread(void * iodata)
color_ostream_proxy out(core->getConsole()); color_ostream_proxy out(core->getConsole());
vector <string> args; vector <string> args;
cheap_tokenise(stuff, args); Core::cheap_tokenise(stuff, args);
if (args.empty()) { if (args.empty()) {
out.printerr("Empty hotkey command.\n"); out.printerr("Empty hotkey command.\n");
continue; continue;
@ -218,7 +220,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
{ {
// cut the input into parts // cut the input into parts
vector <string> parts; vector <string> parts;
cheap_tokenise(command,parts); Core::cheap_tokenise(command,parts);
if(parts.size() == 0) if(parts.size() == 0)
{ {
clueless_counter ++; clueless_counter ++;
@ -701,6 +703,9 @@ bool Core::Init()
virtual_identity::Init(this); virtual_identity::Init(this);
df::global::InitGlobals(); df::global::InitGlobals();
// initialize common lua context
Lua::Core::Init(con);
// create mutex for syncing with interactive tasks // create mutex for syncing with interactive tasks
misc_data_mutex=new mutex(); misc_data_mutex=new mutex();
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
@ -803,6 +808,13 @@ void *Core::GetData( std::string key )
} }
} }
bool Core::isSuspended(void)
{
lock_guard<mutex> lock(d->AccessMutex);
return (d->df_suspend_depth > 0 && d->df_suspend_thread == this_thread::get_id());
}
void Core::Suspend() void Core::Suspend()
{ {
auto tid = this_thread::get_id(); auto tid = this_thread::get_id();
@ -882,10 +894,13 @@ int Core::Update()
Init(); Init();
if(errorstate) if(errorstate)
return -1; return -1;
Lua::Core::Reset(con, "core init");
} }
color_ostream_proxy out(con); color_ostream_proxy out(con);
Lua::Core::Reset(out, "DF code execution");
if (first_update) if (first_update)
plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED); plug_mgr->OnStateChange(out, SC_CORE_INITIALIZED);
@ -976,6 +991,8 @@ int Core::Update()
assert(d->df_suspend_depth == 0); assert(d->df_suspend_depth == 0);
// destroy condition // destroy condition
delete nc; delete nc;
// check lua stack depth
Lua::Core::Reset(con, "suspend");
} }
return 0; return 0;

@ -74,30 +74,7 @@ distribution.
using namespace DFHack; using namespace DFHack;
using namespace DFHack::LuaWrapper; using namespace DFHack::LuaWrapper;
template<class T> int Lua::PushPosXYZ(lua_State *state, df::coord pos)
void push_pointer_vector(lua_State *state, const std::vector<T*> &pvec)
{
lua_createtable(state,pvec.size(),0);
for (size_t i = 0; i < pvec.size(); i++)
{
Lua::PushDFObject(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
template<class T>
T *get_checked_arg(lua_State *state, int arg)
{
luaL_checkany(state, arg);
auto ptr = Lua::GetDFObject<T>(state, arg);
if (!ptr)
luaL_argerror(state, arg, "invalid type");
return ptr;
}
int push_pos(lua_State *state, df::coord pos)
{ {
if (!pos.isValid()) if (!pos.isValid())
{ {
@ -570,9 +547,9 @@ static void OpenModule(lua_State *state, const char *mname,
lua_pop(state, 1); lua_pop(state, 1);
} }
#define WRAPM(module, function) { #function, df::wrap_function(module::function) } #define WRAPM(module, function) { #function, df::wrap_function(module::function,true) }
#define WRAP(function) { #function, df::wrap_function(function) } #define WRAP(function) { #function, df::wrap_function(function,true) }
#define WRAPN(name, function) { #name, df::wrap_function(function) } #define WRAPN(name, function) { #name, df::wrap_function(function,true) }
static const LuaWrapper::FunctionReg dfhack_module[] = { static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAPM(Translation, TranslateName), WRAPM(Translation, TranslateName),
@ -613,7 +590,7 @@ static int job_listNewlyCreated(lua_State *state)
if (Job::listNewlyCreated(&pvec, &nxid)) if (Job::listNewlyCreated(&pvec, &nxid))
{ {
lua_pushinteger(state, nxid); lua_pushinteger(state, nxid);
push_pointer_vector(state, pvec); Lua::PushVector(state, pvec);
return 2; return 2;
} }
else else
@ -642,7 +619,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
static int units_getPosition(lua_State *state) static int units_getPosition(lua_State *state)
{ {
return push_pos(state, Units::getPosition(get_checked_arg<df::unit>(state,1))); return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject<df::unit>(state,1)));
} }
static const luaL_Reg dfhack_units_funcs[] = { static const luaL_Reg dfhack_units_funcs[] = {
@ -673,14 +650,14 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
static int items_getPosition(lua_State *state) static int items_getPosition(lua_State *state)
{ {
return push_pos(state, Items::getPosition(get_checked_arg<df::item>(state,1))); return Lua::PushPosXYZ(state, Items::getPosition(Lua::CheckDFObject<df::item>(state,1)));
} }
static int items_getContainedItems(lua_State *state) static int items_getContainedItems(lua_State *state)
{ {
std::vector<df::item*> pvec; std::vector<df::item*> pvec;
Items::getContainedItems(get_checked_arg<df::item>(state,1),&pvec); Items::getContainedItems(Lua::CheckDFObject<df::item>(state,1),&pvec);
push_pointer_vector(state, pvec); Lua::PushVector(state, pvec);
return 1; return 1;
} }
@ -719,8 +696,8 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
static int maps_listBurrowBlocks(lua_State *state) static int maps_listBurrowBlocks(lua_State *state)
{ {
std::vector<df::map_block*> pvec; std::vector<df::map_block*> pvec;
Maps::listBurrowBlocks(&pvec, get_checked_arg<df::burrow>(state,1)); Maps::listBurrowBlocks(&pvec, Lua::CheckDFObject<df::burrow>(state,1));
push_pointer_vector(state, pvec); Lua::PushVector(state, pvec);
return 1; return 1;
} }

File diff suppressed because it is too large Load Diff

@ -733,8 +733,18 @@ static int lookup_container_field(lua_State *state, int field, const char *mode
* Index verification: number and in range. * Index verification: number and in range.
*/ */
static int check_container_index(lua_State *state, int len, static int check_container_index(lua_State *state, int len,
int fidx, int iidx, const char *mode) int fidx, int iidx, const char *mode,
bool is_insert = false)
{ {
if (is_insert && len >= 0)
{
if (lua_type(state, iidx) == LUA_TSTRING
&& strcmp(lua_tostring(state, iidx), "#") == 0)
return len;
len++;
}
if (!lua_isnumber(state, iidx)) if (!lua_isnumber(state, iidx))
field_error(state, fidx, "invalid index", mode); field_error(state, fidx, "invalid index", mode);
@ -867,8 +877,7 @@ static int method_container_insert(lua_State *state)
auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID);
int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN); int len = id->lua_item_count(state, ptr, container_identity::COUNT_LEN);
if (len >= 0) len++; int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call", true);
int idx = check_container_index(state, len, UPVAL_METHOD_NAME, 2, "call");
if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3)) if (!id->lua_insert(state, UPVAL_METHOD_NAME, ptr, idx, 3))
field_error(state, UPVAL_METHOD_NAME, "not supported", "call"); field_error(state, UPVAL_METHOD_NAME, "not supported", "call");
@ -1036,7 +1045,9 @@ static int meta_call_function(lua_State *state)
int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id) int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id)
{ {
if (lua_gettop(state) != id->getNumArgs()) if (id->adjustArgs())
lua_settop(state, id->getNumArgs());
else if (lua_gettop(state) != id->getNumArgs())
field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke"); field_error(state, UPVAL_METHOD_NAME, "invalid argument count", "invoke");
try { try {
@ -1056,9 +1067,9 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id
} }
/** /**
* Create a closure invoking the given function, and add it to the field table. * Push a closure invoking the given function.
*/ */
static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx, void LuaWrapper::PushFunctionWrapper(lua_State *state, int meta_idx,
const char *name, function_identity_base *fun) const char *name, function_identity_base *fun)
{ {
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN); lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPETABLE_TOKEN);
@ -1069,7 +1080,15 @@ static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
lua_pushfstring(state, "%s()", name); lua_pushfstring(state, "%s()", name);
lua_pushlightuserdata(state, fun); lua_pushlightuserdata(state, fun);
lua_pushcclosure(state, meta_call_function, 4); lua_pushcclosure(state, meta_call_function, 4);
}
/**
* Create a closure invoking the given function, and add it to the field table.
*/
static void AddMethodWrapper(lua_State *state, int meta_idx, int field_idx,
const char *name, function_identity_base *fun)
{
PushFunctionWrapper(state, meta_idx, name, fun);
lua_setfield(state, field_idx, name); lua_setfield(state, field_idx, name);
} }

@ -33,6 +33,7 @@ distribution.
#include "MiscUtils.h" #include "MiscUtils.h"
#include "LuaWrapper.h" #include "LuaWrapper.h"
#include "LuaTools.h"
using namespace DFHack; using namespace DFHack;
@ -380,6 +381,7 @@ command_result Plugin::on_update(color_ostream &out)
if(state == PS_LOADED && plugin_onupdate) if(state == PS_LOADED && plugin_onupdate)
{ {
cr = plugin_onupdate(out); cr = plugin_onupdate(out);
Lua::Core::Reset(out, "plugin_onupdate");
} }
access->lock_sub(); access->lock_sub();
return cr; return cr;
@ -392,6 +394,7 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev
if(state == PS_LOADED && plugin_onstatechange) if(state == PS_LOADED && plugin_onstatechange)
{ {
cr = plugin_onstatechange(out, event); cr = plugin_onstatechange(out, event);
Lua::Core::Reset(out, "plugin_onstatechange");
} }
access->lock_sub(); access->lock_sub();
return cr; return cr;
@ -445,9 +448,7 @@ void Plugin::index_lua(DFLibrary *lib)
for (; cmdlist->name; ++cmdlist) for (; cmdlist->name; ++cmdlist)
{ {
auto &cmd = lua_commands[cmdlist->name]; auto &cmd = lua_commands[cmdlist->name];
if (!cmd) cmd = new LuaCommand; if (!cmd) cmd = new LuaCommand(this,cmdlist->name);
cmd->owner = this;
cmd->name = cmdlist->name;
cmd->command = cmdlist->command; cmd->command = cmdlist->command;
} }
} }
@ -456,12 +457,22 @@ void Plugin::index_lua(DFLibrary *lib)
for (; funlist->name; ++funlist) for (; funlist->name; ++funlist)
{ {
auto &cmd = lua_functions[funlist->name]; auto &cmd = lua_functions[funlist->name];
if (!cmd) cmd = new LuaFunction; if (!cmd) cmd = new LuaFunction(this,funlist->name);
cmd->owner = this;
cmd->name = funlist->name;
cmd->identity = funlist->identity; cmd->identity = funlist->identity;
} }
} }
if (auto evlist = (EventReg*)LookupPlugin(lib, "plugin_lua_events"))
{
for (; evlist->name; ++evlist)
{
auto &cmd = lua_events[evlist->name];
if (!cmd) cmd = new LuaEvent(this,evlist->name);
cmd->handler.identity = evlist->event->get_handler();
cmd->event = evlist->event;
if (cmd->active)
cmd->event->bind(Lua::Core::State, cmd);
}
}
} }
void Plugin::reset_lua() void Plugin::reset_lua()
@ -470,6 +481,11 @@ void Plugin::reset_lua()
it->second->command = NULL; it->second->command = NULL;
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
it->second->identity = NULL; it->second->identity = NULL;
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
{
it->second->handler.identity = NULL;
it->second->event = NULL;
}
} }
int Plugin::lua_cmd_wrapper(lua_State *state) int Plugin::lua_cmd_wrapper(lua_State *state)
@ -513,13 +529,35 @@ void Plugin::open_lua(lua_State *state, int table)
for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it) for (auto it = lua_functions.begin(); it != lua_functions.end(); ++it)
{ {
push_function(state, it->second);
lua_setfield(state, table, it->first.c_str());
}
if (Lua::IsCoreContext(state))
{
for (auto it = lua_events.begin(); it != lua_events.end(); ++it)
{
Lua::MakeEvent(state, it->second);
push_function(state, &it->second->handler);
lua_rawsetp(state, -2, NULL);
it->second->active = true;
if (it->second->event)
it->second->event->bind(state, it->second);
lua_setfield(state, table, it->first.c_str());
}
}
}
void Plugin::push_function(lua_State *state, LuaFunction *fn)
{
lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN); lua_rawgetp(state, LUA_REGISTRYINDEX, &LuaWrapper::DFHACK_TYPETABLE_TOKEN);
lua_pushlightuserdata(state, NULL); lua_pushlightuserdata(state, NULL);
lua_pushfstring(state, "%s.%s()", name.c_str(), it->second->name.c_str()); lua_pushfstring(state, "%s.%s()", name.c_str(), fn->name.c_str());
lua_pushlightuserdata(state, it->second); lua_pushlightuserdata(state, fn);
lua_pushcclosure(state, lua_fun_wrapper, 4); lua_pushcclosure(state, lua_fun_wrapper, 4);
lua_setfield(state, table, it->first.c_str());
}
} }
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core)
@ -603,6 +641,8 @@ void PluginManager::OnStateChange(color_ostream &out, state_change_event event)
{ {
all_plugins[i]->on_state_change(out, event); all_plugins[i]->on_state_change(out, event);
} }
Lua::Core::onStateChange(out, event);
} }
// FIXME: doesn't check name collisions! // FIXME: doesn't check name collisions!

@ -90,6 +90,8 @@ namespace DFHack
static Core instance; static Core instance;
return instance; return instance;
} }
/// check if the activity lock is owned by this thread
bool isSuspended(void);
/// try to acquire the activity lock /// try to acquire the activity lock
void Suspend(void); void Suspend(void);
/// return activity lock /// return activity lock
@ -135,6 +137,8 @@ namespace DFHack
PluginManager *getPluginManager() { return plug_mgr; } PluginManager *getPluginManager() { return plug_mgr; }
static void cheap_tokenise(std::string const& input, std::vector<std::string> &output);
private: private:
DFHack::Console con; DFHack::Console con;

@ -161,15 +161,15 @@ INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5),
public: public:
typedef function_wrapper<T> wrapper; typedef function_wrapper<T> wrapper;
function_identity(T ptr) function_identity(T ptr, bool vararg)
: function_identity_base(wrapper::num_args), ptr(ptr) {}; : function_identity_base(wrapper::num_args, vararg), ptr(ptr) {};
virtual void invoke(lua_State *state, int base) { wrapper::execute(state, base, ptr); } virtual void invoke(lua_State *state, int base) { wrapper::execute(state, base, ptr); }
}; };
template<class T> template<class T>
inline function_identity_base *wrap_function(T ptr) { inline function_identity_base *wrap_function(T ptr, bool vararg = false) {
// bah, but didn't have any idea how to allocate statically // bah, but didn't have any idea how to allocate statically
return new function_identity<T>(ptr); return new function_identity<T>(ptr, vararg);
} }
} }

@ -39,13 +39,17 @@ namespace DFHack
{ {
class DFHACK_EXPORT function_identity_base : public type_identity { class DFHACK_EXPORT function_identity_base : public type_identity {
int num_args; int num_args;
bool vararg;
public: public:
function_identity_base(int num_args) : type_identity(0), num_args(num_args) {}; function_identity_base(int num_args, bool vararg = false)
: type_identity(0), num_args(num_args), vararg(vararg) {};
virtual identity_type type() { return IDTYPE_FUNCTION; } virtual identity_type type() { return IDTYPE_FUNCTION; }
int getNumArgs() { return num_args; } int getNumArgs() { return num_args; }
bool adjustArgs() { return vararg; }
std::string getFullName() { return "function"; } std::string getFullName() { return "function"; }
virtual void invoke(lua_State *state, int base) = 0; virtual void invoke(lua_State *state, int base) = 0;

@ -34,18 +34,33 @@ distribution.
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
namespace DFHack { namespace Lua { namespace DFHack {
class function_identity_base;
}
namespace DFHack {namespace Lua {
/** /**
* Create or initialize a lua interpreter with access to DFHack tools. * Create or initialize a lua interpreter with access to DFHack tools.
*/ */
DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL); DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL);
/** /**
* Load a module using require(). * Load a module using require(). Leaves the stack as is.
*/ */
DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state, DFHACK_EXPORT bool Require(color_ostream &out, lua_State *state,
const std::string &module, bool setglobal = false); const std::string &module, bool setglobal = false);
/**
* Push the module table, loading it using require() if necessary.
*/
DFHACK_EXPORT bool PushModule(color_ostream &out, lua_State *state, const char *module);
/**
* Push the public object name exported by the module. Uses PushModule.
*/
DFHACK_EXPORT bool PushModulePublic(color_ostream &out, lua_State *state,
const char *module, const char *name);
/** /**
* Check if the object at the given index is NIL or NULL. * Check if the object at the given index is NIL or NULL.
*/ */
@ -79,6 +94,12 @@ namespace DFHack { namespace Lua {
*/ */
DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false); DFHACK_EXPORT void *GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false);
/**
* Check that the value is a wrapped DF object of the given type, and if so return the pointer.
* Otherwise throw an argument type error.
*/
DFHACK_EXPORT void *CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type = false);
/** /**
* Assign the value at val_index to the target of given identity using df.assign(). * Assign the value at val_index to the target of given identity using df.assign().
* Return behavior is of SafeCall below. * Return behavior is of SafeCall below.
@ -102,6 +123,14 @@ namespace DFHack { namespace Lua {
return (T*)GetDFObject(state, df::identity_traits<T>::get(), val_index, exact_type); return (T*)GetDFObject(state, df::identity_traits<T>::get(), val_index, exact_type);
} }
/**
* Check that the value is a wrapped DF object of the correct type, and if so return the pointer. Otherwise throw an argument type error.
*/
template<class T>
T *CheckDFObject(lua_State *state, int val_index, bool exact_type = false) {
return (T*)CheckDFObject(state, df::identity_traits<T>::get(), val_index, exact_type);
}
/** /**
* Assign the value at val_index to the target using df.assign(). * Assign the value at val_index to the target using df.assign().
*/ */
@ -110,12 +139,56 @@ namespace DFHack { namespace Lua {
return AssignDFObject(out, state, df::identity_traits<T>::get(), target, val_index, perr); return AssignDFObject(out, state, df::identity_traits<T>::get(), target, val_index, perr);
} }
/**
* Check if the status is a success, i.e. LUA_OK or LUA_YIELD.
*/
inline bool IsSuccess(int status) {
return (status == LUA_OK || status == LUA_YIELD);
}
// Internal helper
template<int (*cb)(lua_State*,int,int)>
int TailPCallK_Thunk(lua_State *state) {
int tmp;
int rv = lua_getctx(state, &tmp);
return cb(state, rv, tmp);
}
/**
* A utility for using the restartable pcall feature more conveniently;
* specifically, the callback is called with the same kind of arguments
* in both yield and non-yield case.
*/
template<int (*cb)(lua_State*,int,int)>
int TailPCallK(lua_State *state, int narg, int nret, int errfun, int ctx) {
int rv = lua_pcallk(state, narg, nret, errfun, ctx, &TailPCallK_Thunk<cb>);
return cb(state, rv, ctx);
}
/** /**
* Invoke lua function via pcall. Returns true if success. * 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. * If an error is signalled, and perr is true, it is printed and popped from the stack.
*/ */
DFHACK_EXPORT bool SafeCall(color_ostream &out, lua_State *state, int nargs, int nres, bool perr = true); DFHACK_EXPORT bool SafeCall(color_ostream &out, lua_State *state, int nargs, int nres, bool perr = true);
/**
* Pops a function from the top of the stack, and pushes a new coroutine.
*/
DFHACK_EXPORT lua_State *NewCoroutine(lua_State *state);
/**
* Resume the coroutine using nargs values from state from. Results or the error are moved back.
* If an error is signalled, and perr is true, it is printed and popped from the stack.
* Returns the lua_resume return value.
*/
DFHACK_EXPORT int SafeResume(color_ostream &out, lua_State *from, lua_State *thread, int nargs, int nres, bool perr = true);
/**
* Works just like SafeCall, only expects a coroutine on the stack
* instead of a function. Returns the lua_resume return value.
*/
DFHACK_EXPORT int SafeResume(color_ostream &out, lua_State *from, int nargs, int nres, bool perr = true);
/** /**
* Parse code from string with debug_tag and env_idx, then call it using SafeCall. * Parse code from string with debug_tag and env_idx, then call it using SafeCall.
* In case of error, it is either left on the stack, or printed like SafeCall does. * In case of error, it is either left on the stack, or printed like SafeCall does.
@ -131,8 +204,168 @@ namespace DFHack { namespace Lua {
/** /**
* Run an interactive interpreter loop if possible, or return false. * Run an interactive interpreter loop if possible, or return false.
* Uses RunCoreQueryLoop internally.
*/ */
DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state, DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state,
const char *prompt = NULL, int env = 0, const char *hfile = NULL); const char *prompt = NULL, const char *hfile = NULL);
/**
* Run an interactive prompt loop. All access to the lua state
* is done inside CoreSuspender, while waiting for input happens
* without the suspend lock.
*/
DFHACK_EXPORT bool RunCoreQueryLoop(color_ostream &out, lua_State *state,
bool (*init)(color_ostream&, lua_State*, void*),
void *arg);
/**
* Push utility functions
*/
#if 0
#define NUMBER_PUSH(type) inline void Push(lua_State *state, type value) { lua_pushnumber(state, value); }
NUMBER_PUSH(char)
NUMBER_PUSH(int8_t) NUMBER_PUSH(uint8_t)
NUMBER_PUSH(int16_t) NUMBER_PUSH(uint16_t)
NUMBER_PUSH(int32_t) NUMBER_PUSH(uint32_t)
NUMBER_PUSH(int64_t) NUMBER_PUSH(uint64_t)
NUMBER_PUSH(float) NUMBER_PUSH(double)
#undef NUMBER_PUSH
#else
template<class T> inline void Push(lua_State *state, T value) {
lua_pushnumber(state, lua_Number(value));
}
#endif
inline void Push(lua_State *state, bool value) {
lua_pushboolean(state, value);
}
inline void Push(lua_State *state, const std::string &str) {
lua_pushlstring(state, str.data(), str.size());
}
inline void Push(lua_State *state, df::coord &obj) { PushDFObject(state, &obj); }
inline void Push(lua_State *state, df::coord2d &obj) { PushDFObject(state, &obj); }
template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr);
}
template<class T>
void PushVector(lua_State *state, const T &pvec)
{
lua_createtable(state,pvec.size(),0);
for (size_t i = 0; i < pvec.size(); i++)
{
Push(state, pvec[i]);
lua_rawseti(state, -2, i+1);
}
}
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
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 for the common lua interpreter state.
* All accesses must be done under CoreSuspender.
*/
namespace Core {
DFHACK_EXPORT extern lua_State *State;
// Not exported; for use by the Core class
void Init(color_ostream &out);
void Reset(color_ostream &out, const char *where);
// Events signalled by the core
void onStateChange(color_ostream &out, int code);
template<class T> inline void Push(T &arg) { Lua::Push(State, arg); }
template<class T> inline void Push(const T &arg) { Lua::Push(State, arg); }
template<class T> inline void PushVector(const T &arg) { Lua::PushVector(State, arg); }
inline bool SafeCall(color_ostream &out, int nargs, int nres, bool perr = true) {
return Lua::SafeCall(out, State, nargs, nres, perr);
}
inline bool PushModule(color_ostream &out, const char *module) {
return Lua::PushModule(out, State, module);
}
inline bool PushModulePublic(color_ostream &out, const char *module, const char *name) {
return Lua::PushModulePublic(out, State, module, name);
}
}
class DFHACK_EXPORT Notification {
lua_State *state;
void *key;
function_identity_base *handler;
public:
Notification(function_identity_base *handler = NULL)
: state(NULL), key(NULL), handler(handler) {}
lua_State *get_state() { return state; }
function_identity_base *get_handler() { return handler; }
void invoke(color_ostream &out, int nargs);
void bind(lua_State *state, const char *name);
void bind(lua_State *state, void *key);
};
}} }}
#define DEFINE_LUA_EVENT_0(name, handler) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out) { \
handler(out); \
if (name##_event.get_state()) { \
name##_event.invoke(out, 0); \
} \
}
#define DEFINE_LUA_EVENT_1(name, handler, arg_type1) \
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()) { \
DFHack::Lua::Push(state, arg1); \
name##_event.invoke(out, 1); \
} \
}
#define DEFINE_LUA_EVENT_2(name, handler, arg_type1, arg_type2) \
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()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
name##_event.invoke(out, 2); \
} \
}
#define DEFINE_LUA_EVENT_3(name, handler, arg_type1, arg_type2, arg_type3) \
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()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
name##_event.invoke(out, 3); \
} \
}
#define DEFINE_LUA_EVENT_4(name, handler, arg_type1, arg_type2, arg_type3, arg_type4) \
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()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
DFHack::Lua::Push(state, arg4); \
name##_event.invoke(out, 4); \
} \
}

@ -222,6 +222,12 @@ namespace DFHack { namespace LuaWrapper {
*/ */
void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum);
/**
* Push a closure invoking the given function.
*/
void PushFunctionWrapper(lua_State *state, int meta_idx,
const char *name, function_identity_base *fun);
/** /**
* Wrap functions and add them to the table on the top of the stack. * Wrap functions and add them to the table on the top of the stack.
*/ */

@ -51,6 +51,9 @@ namespace DFHack
class virtual_identity; class virtual_identity;
class RPCService; class RPCService;
class function_identity_base; class function_identity_base;
namespace Lua {
class Notification;
}
// anon type, pretty much // anon type, pretty much
struct DFLibrary; struct DFLibrary;
@ -64,13 +67,13 @@ namespace DFHack
enum state_change_event enum state_change_event
{ {
SC_WORLD_LOADED, SC_WORLD_LOADED = 0,
SC_WORLD_UNLOADED, SC_WORLD_UNLOADED = 1,
SC_MAP_LOADED, SC_MAP_LOADED = 2,
SC_MAP_UNLOADED, SC_MAP_UNLOADED = 3,
SC_VIEWSCREEN_CHANGED, SC_VIEWSCREEN_CHANGED = 4,
SC_CORE_INITIALIZED, SC_CORE_INITIALIZED = 5,
SC_BEGIN_UNLOAD SC_BEGIN_UNLOAD = 6
}; };
struct DFHACK_EXPORT CommandReg { struct DFHACK_EXPORT CommandReg {
const char *name; const char *name;
@ -80,6 +83,10 @@ namespace DFHack
const char *name; const char *name;
function_identity_base *identity; function_identity_base *identity;
}; };
struct DFHACK_EXPORT EventReg {
const char *name;
Lua::Notification *event;
};
struct DFHACK_EXPORT PluginCommand struct DFHACK_EXPORT PluginCommand
{ {
typedef command_result (*command_function)(color_ostream &out, std::vector <std::string> &); typedef command_result (*command_function)(color_ostream &out, std::vector <std::string> &);
@ -178,6 +185,7 @@ namespace DFHack
Plugin *owner; Plugin *owner;
std::string name; std::string name;
int (*command)(lua_State *state); int (*command)(lua_State *state);
LuaCommand(Plugin *owner, std::string name) : owner(owner), name(name) {}
}; };
std::map<std::string, LuaCommand*> lua_commands; std::map<std::string, LuaCommand*> lua_commands;
static int lua_cmd_wrapper(lua_State *state); static int lua_cmd_wrapper(lua_State *state);
@ -186,9 +194,19 @@ namespace DFHack
Plugin *owner; Plugin *owner;
std::string name; std::string name;
function_identity_base *identity; function_identity_base *identity;
LuaFunction(Plugin *owner, std::string name) : owner(owner), name(name) {}
}; };
std::map<std::string, LuaFunction*> lua_functions; std::map<std::string, LuaFunction*> lua_functions;
static int lua_fun_wrapper(lua_State *state); 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) {}
};
std::map<std::string, LuaEvent*> lua_events;
void index_lua(DFLibrary *lib); void index_lua(DFLibrary *lib);
void reset_lua(); void reset_lua();
@ -253,7 +271,10 @@ namespace DFHack
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =
#define DFHACK_PLUGIN_LUA_FUNCTIONS \ #define DFHACK_PLUGIN_LUA_FUNCTIONS \
DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] = DFhackCExport const DFHack::FunctionReg plugin_lua_functions[] =
#define DFHACK_PLUGIN_LUA_EVENTS \
DFhackCExport const DFHack::EventReg plugin_lua_events[] =
#define DFHACK_LUA_COMMAND(name) { #name, name } #define DFHACK_LUA_COMMAND(name) { #name, name }
#define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name) } #define DFHACK_LUA_FUNCTION(name) { #name, df::wrap_function(name,true) }
#define DFHACK_LUA_EVENT(name) { #name, &name##_event }
#define DFHACK_LUA_END { NULL, NULL } #define DFHACK_LUA_END { NULL, NULL }

@ -66,7 +66,7 @@ namespace DFHack
}; };
struct RPCMessageHeader { struct RPCMessageHeader {
static const int MAX_MESSAGE_SIZE = 8*1048756; static const int MAX_MESSAGE_SIZE = 8*1048576;
int16_t id; int16_t id;
int32_t size; int32_t size;

@ -21,6 +21,17 @@ COLOR_LIGHTMAGENTA = 13
COLOR_YELLOW = 14 COLOR_YELLOW = 14
COLOR_WHITE = 15 COLOR_WHITE = 15
-- Events
if dfhack.is_core_context then
SC_WORLD_LOADED = 0
SC_WORLD_UNLOADED = 1
SC_MAP_LOADED = 2
SC_MAP_UNLOADED = 3
SC_VIEWSCREEN_CHANGED = 4
SC_CORE_INITIALIZED = 5
end
-- Error handling -- Error handling
safecall = dfhack.safecall safecall = dfhack.safecall
@ -105,6 +116,10 @@ function xyz2pos(x,y,z)
end end
end end
function dfhack.event:__tostring()
return "<event>"
end
function dfhack.persistent:__tostring() function dfhack.persistent:__tostring()
return "<persistent "..self.entry_id..":"..self.key.."=\"" return "<persistent "..self.entry_id..":"..self.key.."=\""
..self.value.."\":"..table.concat(self.ints,",")..">" ..self.value.."\":"..table.concat(self.ints,",")..">"
@ -124,5 +139,83 @@ function dfhack.maps.getTileSize()
return map.x_count, map.y_count, map.z_count return map.x_count, map.y_count, map.z_count
end end
-- Interactive
local print_banner = true
function dfhack.interpreter(prompt,hfile,env)
if not dfhack.is_interactive() then
return nil, 'not interactive'
end
print("Type quit to exit interactive lua interpreter.")
if print_banner then
print("Shortcuts:\n"..
" '= foo' => '_1,_2,... = foo'\n"..
" '! foo' => 'print(foo)'\n"..
"Both save the first result as '_'.")
print_banner = false
end
local prompt_str = "["..(prompt or 'lua').."]# "
local prompt_env = {}
local t_prompt
local vcnt = 1
setmetatable(prompt_env, { __index = env or _G })
local cmdlinelist={}
while true do
local cmdline = dfhack.lineedit(t_prompt or prompt_str, hfile)
if cmdline == nil or cmdline == 'quit' then
break
elseif cmdline ~= '' then
local pfix = string.sub(cmdline,1,1)
if pfix == '!' or pfix == '=' then
cmdline = 'return '..string.sub(cmdline,2)
end
table.insert(cmdlinelist,cmdline)
local code,err = load(table.concat(cmdlinelist,'\n'), '=(interactive)', 't', prompt_env)
if code == nil then
if err:sub(-5)=="<eof>" then
t_prompt="[cont]"
else
dfhack.printerr(err)
cmdlinelist={}
t_prompt=nil
end
else
cmdlinelist={}
t_prompt=nil
local data = table.pack(safecall(code))
if data[1] and data.n > 1 then
prompt_env._ = data[2]
if pfix == '!' then
safecall(print, table.unpack(data,2,data.n))
else
for i=2,data.n do
local varname = '_'..vcnt
prompt_env[varname] = data[i]
dfhack.print(varname..' = ')
safecall(print, data[i])
vcnt = vcnt + 1
end
end
end
end
end
end
return true
end
-- Feed the table back to the require() mechanism. -- Feed the table back to the require() mechanism.
return dfhack return dfhack

@ -1 +1 @@
Subproject commit a545167050fee9eedd5e319b518d961f933154a3 Subproject commit e8036d3f13c6be0141899baae90f605ad11d5385

@ -6,6 +6,9 @@ class Brush
public: public:
virtual ~Brush(){}; virtual ~Brush(){};
virtual coord_vec points(MapExtras::MapCache & mc,DFHack::DFCoord start) = 0; virtual coord_vec points(MapExtras::MapCache & mc,DFHack::DFCoord start) = 0;
virtual std::string str() const {
return "unknown";
}
}; };
/** /**
* generic 3D rectangle brush. you can specify the dimensions of * generic 3D rectangle brush. you can specify the dimensions of
@ -56,6 +59,13 @@ public:
return v; return v;
}; };
~RectangleBrush(){}; ~RectangleBrush(){};
std::string str() const {
if (x_ == 1 && y_ == 1 && z_ == 1) {
return "point";
} else {
return "rectangle";
}
}
private: private:
int x_, y_, z_; int x_, y_, z_;
int cx_, cy_, cz_; int cx_, cy_, cz_;
@ -89,6 +99,9 @@ public:
} }
return v; return v;
}; };
std::string str() const {
return "block";
}
}; };
/** /**
@ -117,6 +130,9 @@ public:
} }
return v; return v;
}; };
std::string str() const {
return "column";
}
}; };
/** /**
@ -168,6 +184,9 @@ public:
return v; return v;
} }
std::string str() const {
return "flood";
}
private: private:
void maybeFlood(DFCoord c, std::stack<DFCoord> &to_flood, MapExtras::MapCache &mc) { void maybeFlood(DFCoord c, std::stack<DFCoord> &to_flood, MapExtras::MapCache &mc) {
if (mc.testCoord(c)) { if (mc.testCoord(c)) {
@ -176,3 +195,13 @@ private:
} }
Core *c_; Core *c_;
}; };
inline std::ostream &operator<<(std::ostream &stream, const Brush& brush) {
stream << brush.str();
return stream;
}
inline std::ostream &operator<<(std::ostream &stream, const Brush* brush) {
stream << brush->str();
return stream;
}

@ -102,6 +102,8 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(feature feature.cpp) DFHACK_PLUGIN(feature feature.cpp)
DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(lair lair.cpp)
DFHACK_PLUGIN(zone zone.cpp) DFHACK_PLUGIN(zone zone.cpp)
DFHACK_PLUGIN(catsplosion catsplosion.cpp)
DFHACK_PLUGIN(regrass regrass.cpp)
# this one exports functions to lua # this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
# not yet. busy with other crud again... # not yet. busy with other crud again...

@ -121,6 +121,12 @@ command_result lua_run_file (color_ostream &out, std::vector <std::string> &para
} }
command_result lua_run (color_ostream &out, std::vector <std::string> &parameters) command_result lua_run (color_ostream &out, std::vector <std::string> &parameters)
{ {
if (!parameters.empty() && parameters[0] == "--core-context")
{
Lua::InterpreterLoop(out, Lua::Core::State, "core lua");
return CR_OK;
}
mymutex->lock(); mymutex->lock();
lua::state s=lua::glua::Get(); lua::state s=lua::glua::Get();

@ -5,6 +5,7 @@
#include "Error.h" #include "Error.h"
#include "DataFuncs.h" #include "DataFuncs.h"
#include "LuaTools.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Job.h" #include "modules/Job.h"
@ -121,6 +122,8 @@ static int name_burrow_id = -1;
static void handle_burrow_rename(color_ostream &out, df::burrow *burrow); static void handle_burrow_rename(color_ostream &out, df::burrow *burrow);
DEFINE_LUA_EVENT_1(onBurrowRename, handle_burrow_rename, df::burrow*);
static void detect_burrow_renames(color_ostream &out) static void detect_burrow_renames(color_ostream &out)
{ {
if (ui->main.mode == ui_sidebar_mode::Burrows && if (ui->main.mode == ui_sidebar_mode::Burrows &&
@ -134,7 +137,7 @@ static void detect_burrow_renames(color_ostream &out)
auto burrow = df::burrow::find(name_burrow_id); auto burrow = df::burrow::find(name_burrow_id);
name_burrow_id = -1; name_burrow_id = -1;
if (burrow) if (burrow)
handle_burrow_rename(out, burrow); onBurrowRename(out, burrow);
} }
} }
@ -151,6 +154,9 @@ static std::map<int,DigJob> diggers;
static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos, static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord pos,
df::tiletype old_tile, df::tiletype new_tile); df::tiletype old_tile, df::tiletype new_tile);
DEFINE_LUA_EVENT_4(onDigComplete, handle_dig_complete,
df::job_type, df::coord, df::tiletype, df::tiletype);
static void detect_digging(color_ostream &out) static void detect_digging(color_ostream &out)
{ {
for (auto it = diggers.begin(); it != diggers.end();) for (auto it = diggers.begin(); it != diggers.end();)
@ -172,7 +178,7 @@ static void detect_digging(color_ostream &out)
if (new_tile != it->second.old_tile) if (new_tile != it->second.old_tile)
{ {
handle_dig_complete(out, it->second.job, pos, it->second.old_tile, new_tile); onDigComplete(out, it->second.job, pos, it->second.old_tile, new_tile);
//if (worker && !worker->job.current_job) //if (worker && !worker->job.current_job)
// worker->counters.think_counter = worker->counters.job_counter = 0; // worker->counters.think_counter = worker->counters.job_counter = 0;
@ -410,6 +416,17 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord
} }
} }
static void renameBurrow(color_ostream &out, df::burrow *burrow, std::string name)
{
CHECK_NULL_POINTER(burrow);
// The event makes this absolutely necessary
CoreSuspender suspend;
burrow->name = name;
onBurrowRename(out, burrow);
}
static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false) static df::burrow *findByName(color_ostream &out, std::string name, bool silent = false)
{ {
int id = -1; int id = -1;
@ -552,6 +569,7 @@ static bool setTilesByKeyword(df::burrow *target, std::string name, bool enable)
} }
DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(renameBurrow),
DFHACK_LUA_FUNCTION(findByName), DFHACK_LUA_FUNCTION(findByName),
DFHACK_LUA_FUNCTION(copyUnits), DFHACK_LUA_FUNCTION(copyUnits),
DFHACK_LUA_FUNCTION(copyTiles), DFHACK_LUA_FUNCTION(copyTiles),
@ -559,6 +577,12 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_END DFHACK_LUA_END
}; };
DFHACK_PLUGIN_LUA_EVENTS {
DFHACK_LUA_EVENT(onBurrowRename),
DFHACK_LUA_EVENT(onDigComplete),
DFHACK_LUA_END
};
static command_result burrow(color_ostream &out, vector <string> &parameters) static command_result burrow(color_ostream &out, vector <string> &parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;

@ -35,9 +35,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
{ {
// Fill the command list with your commands. // Fill the command list with your commands.
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"catsplosion", "Do nothing, look pretty.", "catsplosion", "Make cats just /multiply/.",
catsplosion, false, catsplosion, false,
" This command does nothing at all. For now.\n" " Makes cats abnormally abundant, if you provide some base population ;)\n"
)); ));
return CR_OK; return CR_OK;
} }

@ -7,12 +7,10 @@ DFHACK_PLUGIN(kittens kittens.cpp)
#DFHACK_PLUGIN(itemhacks itemhacks.cpp) #DFHACK_PLUGIN(itemhacks itemhacks.cpp)
DFHACK_PLUGIN(notes notes.cpp) DFHACK_PLUGIN(notes notes.cpp)
DFHACK_PLUGIN(memview memview.cpp) DFHACK_PLUGIN(memview memview.cpp)
DFHACK_PLUGIN(catsplosion catsplosion.cpp)
DFHACK_PLUGIN(buildprobe buildprobe.cpp) DFHACK_PLUGIN(buildprobe buildprobe.cpp)
DFHACK_PLUGIN(tilesieve tilesieve.cpp) DFHACK_PLUGIN(tilesieve tilesieve.cpp)
DFHACK_PLUGIN(frozen frozen.cpp) DFHACK_PLUGIN(frozen frozen.cpp)
DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp)
#DFHACK_PLUGIN(tiles tiles.cpp) #DFHACK_PLUGIN(tiles tiles.cpp)
DFHACK_PLUGIN(regrass regrass.cpp)
DFHACK_PLUGIN(counters counters.cpp) DFHACK_PLUGIN(counters counters.cpp)

@ -122,6 +122,7 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
str << ":" << amount << ":" << flowmode << ":" << setmode << "]#"; str << ":" << amount << ":" << flowmode << ":" << setmode << "]#";
if(out.lineedit(str.str(),command,liquids_hist) == -1) if(out.lineedit(str.str(),command,liquids_hist) == -1)
return CR_FAILURE; return CR_FAILURE;
liquids_hist.add(command);
if(command=="help" || command == "?") if(command=="help" || command == "?")
{ {

@ -1 +1 @@
Subproject commit d1c87ec273ef5c928989437379943a9a71a1d4e0 Subproject commit 15e3b726c3e68d2985aecd95d2a33bf4550caaa1

@ -1,4 +1,24 @@
// Plugin tiletypes
// //
// This plugin allows fine editing of individual game tiles (expect for
// changing material subtypes).
//
// Commands:
// tiletypes - runs the interractive interpreter
// tiletypes-command - run the given command
// (intended to be mapped to a hotkey or used from dfhack-run)
// tiletypes-here - runs the execute method with the last settings from
// tiletypes(-command), including brush!
// (intended to be mapped to a hotkey)
// tiletypes-here-point - runs the execute method with the last settings from
// tiletypes(-command), except with a point brush!
// (intended to be mapped to a hotkey)
// Options (everything but tiletypes-command):
// ?, help - print some help
//
// Options (tiletypes-command):
// (anything) - run the given command
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <map> #include <map>
@ -25,39 +45,120 @@ using namespace MapExtras;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
//zilpin: These two functions were giving me compile errors in VS2008, so I cheated with the C style loop below, just to get it to build. CommandHistory tiletypes_hist;
//Original code is commented out.
void tolower(std::string &str)
{
//The C++ way...
//std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::tolower<char> ), std::locale("")));
//The C way... command_result df_tiletypes (color_ostream &out, vector <string> & parameters);
for(char *c=(char *)str.c_str(); *c; ++c) command_result df_tiletypes_command (color_ostream &out, vector <string> & parameters);
{ command_result df_tiletypes_here (color_ostream &out, vector <string> & parameters);
*c = tolower(*c); command_result df_tiletypes_here_point (color_ostream &out, vector <string> & parameters);
}
DFHACK_PLUGIN("tiletypes");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
tiletypes_hist.load("tiletypes.history");
commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true));
commands.push_back(PluginCommand("tiletypes-command", "Run the given commands (seperated by ' ; '; an empty command is the same as run).", df_tiletypes_command));
commands.push_back(PluginCommand("tiletypes-here", "Use the last settings from tiletypes, including brush, at cursor location.", df_tiletypes_here));
commands.push_back(PluginCommand("tiletypes-here-point", "Use the last settings from tiletypes, not including brush, at cursor location.", df_tiletypes_here_point));
return CR_OK;
} }
void toupper(std::string &str) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
//std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::toupper<char>), std::locale(""))); tiletypes_hist.save("tiletypes.history");
for(char *c=(char *)str.c_str(); *c; ++c) return CR_OK;
{
*c = toupper(*c);
}
} }
int toint(const std::string &str, int failValue = 0) void help( color_ostream & out, std::vector<std::string> &commands, int start, int end)
{ {
std::istringstream ss(str); std::string option = commands.size() > start ? commands[start] : "";
int valInt; if (option.empty())
ss >> valInt;
if (ss.fail())
{ {
return failValue; out << "Commands:" << std::endl
<< " quit / q : quit" << std::endl
<< " filter / f [options] : change filter options" << std::endl
<< " paint / p [options] : change paint options" << std::endl
<< " point / p : set point brush" << std::endl
<< " range / r [w] [h] [z] : set range brush" << std::endl
<< " block : set block brush" << std::endl
<< " column : set column brush" << std::endl
<< " run / (empty) : paint!" << std::endl
<< std::endl
<< "Filter/paint options:" << std::endl
<< " Shape / sh / s: set tile shape information" << std::endl
<< " Material / mat / m: set tile material information" << std::endl
<< " Special / sp: set special tile information" << std::endl
<< " Variant / var / v: set variant tile information" << std::endl
<< " All / a: set the four above at the same time (no ANY support)" << std::endl
<< " Designated / d: set designated flag" << std::endl
<< " Hidden / h: set hidden flag" << std::endl
<< " Light / l: set light flag" << std::endl
<< " Subterranean / st: set subterranean flag" << std::endl
<< " Skyview / sv: set skyview flag" << std::endl
<< "See help [option] for more information" << std::endl;
}
else if (option == "shape" || option == "s" ||option == "sh")
{
out << "Available shapes:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_shape,i)
{
out << " " << ENUM_KEY_STR(tiletype_shape,i) << std::endl;
}
}
else if (option == "material"|| option == "mat" ||option == "m")
{
out << "Available materials:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_material,i)
{
out << " " << ENUM_KEY_STR(tiletype_material,i) << std::endl;
}
}
else if (option == "special" || option == "sp")
{
out << "Available specials:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_special,i)
{
out << " " << ENUM_KEY_STR(tiletype_special,i) << std::endl;
}
}
else if (option == "variant" || option == "var" || option == "v")
{
out << "Available variants:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_variant,i)
{
out << " " << ENUM_KEY_STR(tiletype_variant,i) << std::endl;
}
}
else if (option == "designated" || option == "d")
{
out << "Available designated flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "hidden" || option == "h")
{
out << "Available hidden flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "light" || option == "l")
{
out << "Available light flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "subterranean" || option == "st")
{
out << "Available subterranean flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "skyview" || option == "sv")
{
out << "Available skyview flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
} }
return valInt;
} }
struct TileType struct TileType
@ -217,12 +318,116 @@ std::ostream &operator<<(std::ostream &stream, const TileType &paint)
return stream; return stream;
} }
bool processTileType(TileType &paint, const std::string &option, const std::string &value) static TileType filter, paint;
static Brush *brush = new RectangleBrush(1,1);
void printState(color_ostream &out)
{
out << "Filter: " << filter << std::endl
<< "Paint: " << paint << std::endl
<< "Brush: " << brush << std::endl;
}
//zilpin: These two functions were giving me compile errors in VS2008, so I cheated with the C style loop below, just to get it to build.
//Original code is commented out.
void tolower(std::string &str)
{
//The C++ way...
//std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::tolower<char> ), std::locale("")));
//The C way...
for(char *c=(char *)str.c_str(); *c; ++c)
{
*c = tolower(*c);
}
}
void toupper(std::string &str)
{
//std::transform(str.begin(), str.end(), str.begin(), std::bind2nd(std::ptr_fun(&std::toupper<char>), std::locale("")));
for(char *c=(char *)str.c_str(); *c; ++c)
{
*c = toupper(*c);
}
}
int toint(const std::string &str, int failValue = 0)
{
std::istringstream ss(str);
int valInt;
ss >> valInt;
if (ss.fail())
{
return failValue;
}
return valInt;
}
bool tryShape(std::string value, TileType &paint)
{
FOR_ENUM_ITEMS(tiletype_shape,i)
{
if (value == ENUM_KEY_STR(tiletype_shape,i))
{
paint.shape = i;
return true;
}
}
return false;
}
bool tryMaterial(std::string value, TileType &paint)
{
FOR_ENUM_ITEMS(tiletype_material, i)
{
if (value == ENUM_KEY_STR(tiletype_material,i))
{
paint.material = i;
return true;
}
}
return false;
}
bool trySpecial(std::string value, TileType &paint)
{
FOR_ENUM_ITEMS(tiletype_special, i)
{
if (value == ENUM_KEY_STR(tiletype_special,i))
{
paint.special = i;
return true;
}
}
return false;
}
bool tryVariant(std::string value, TileType &paint)
{
FOR_ENUM_ITEMS(tiletype_variant, i)
{
if (value == ENUM_KEY_STR(tiletype_variant,i))
{
paint.variant = i;
return true;
}
}
return false;
}
bool processTileType(color_ostream & out, TileType &paint, std::vector<std::string> &params, int start, int end)
{ {
std::string val = value; if (params.size() < start + 2)
toupper(val); {
return false;
}
int loc = start;
std::string option = params[loc++];
std::string value = params[loc++];
toupper(value);
int valInt; int valInt;
if (val == "ANY") if (value == "ANY")
{ {
valInt = -1; valInt = -1;
} }
@ -241,19 +446,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
FOR_ENUM_ITEMS(tiletype_shape,i) if (!tryShape(value, paint))
{
if (val == ENUM_KEY_STR(tiletype_shape,i))
{
paint.shape = i;
found = true;
break;
}
}
if (!found)
{ {
std::cout << "Unknown tile shape: " << value << std::endl; out << "Unknown tile shape: " << value << std::endl;
} }
} }
} }
@ -266,19 +461,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
FOR_ENUM_ITEMS(tiletype_material, i) if (!tryMaterial(value, paint))
{
if (val == ENUM_KEY_STR(tiletype_material,i))
{
paint.material = i;
found = true;
break;
}
}
if (!found)
{ {
std::cout << "Unknown tile material: " << value << std::endl; out << "Unknown tile material: " << value << std::endl;
} }
} }
} }
@ -291,19 +476,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
FOR_ENUM_ITEMS(tiletype_special, i) if (!trySpecial(value, paint))
{
if (val == ENUM_KEY_STR(tiletype_special,i))
{
paint.special = i;
found = true;
break;
}
}
if (!found)
{ {
std::cout << "Unknown tile special: " << value << std::endl; out << "Unknown tile special: " << value << std::endl;
} }
} }
} }
@ -316,19 +491,9 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
FOR_ENUM_ITEMS(tiletype_variant, i) if (!tryVariant(value, paint))
{
if (val == ENUM_KEY_STR(tiletype_variant,i))
{
paint.variant = i;
found = true;
break;
}
}
if (!found)
{ {
std::cout << "Unknown tile variant: " << value << std::endl; out << "Unknown tile variant: " << value << std::endl;
} }
} }
} }
@ -341,7 +506,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
std::cout << "Unknown designation flag: " << value << std::endl; out << "Unknown designation flag: " << value << std::endl;
} }
} }
else if (option == "hidden" || option == "h") else if (option == "hidden" || option == "h")
@ -353,7 +518,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
std::cout << "Unknown hidden flag: " << value << std::endl; out << "Unknown hidden flag: " << value << std::endl;
} }
} }
else if (option == "light" || option == "l") else if (option == "light" || option == "l")
@ -365,7 +530,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
std::cout << "Unknown light flag: " << value << std::endl; out << "Unknown light flag: " << value << std::endl;
} }
} }
else if (option == "subterranean" || option == "st") else if (option == "subterranean" || option == "st")
@ -377,7 +542,7 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
std::cout << "Unknown subterranean flag: " << value << std::endl; out << "Unknown subterranean flag: " << value << std::endl;
} }
} }
else if (option == "skyview" || option == "sv") else if (option == "skyview" || option == "sv")
@ -389,264 +554,65 @@ bool processTileType(TileType &paint, const std::string &option, const std::stri
} }
else else
{ {
std::cout << "Unknown skyview flag: " << value << std::endl; out << "Unknown skyview flag: " << value << std::endl;
}
}
else if (option == "all" || option == "a")
{
loc--;
for (; loc < end; loc++)
{
std::string param = params[loc];
toupper(param);
if (!(tryShape(param, paint) || tryMaterial(param, paint) ||
trySpecial(param, paint) || tryVariant(param, paint)))
{
out << "Unknown description: '" << param << "'" << std::endl;
break;
}
} }
found = true;
} }
else else
{ {
std::cout << "Unknown option: '" << option << "'" << std::endl; out << "Unknown option: '" << option << "'" << std::endl;
} }
return found; return found;
} }
void help( std::ostream & out, const std::string &option) command_result executePaintJob(color_ostream &out)
{ {
if (option.empty()) if (paint.empty())
{ {
out << "Commands:" << std::endl out.printerr("Set the paint first.\n");
<< " quit / q : quit" << std::endl
<< " filter / f [options] : change filter options" << std::endl
<< " paint / p [options] : change paint options" << std::endl
<< " point / p : set point brush" << std::endl
<< " range / r : set range brush" << std::endl
<< " block : set block brush" << std::endl
<< " column : set column brush" << std::endl
<< std::endl
<< "Filter/paint options:" << std::endl
<< " Shape / sh / s: set tile shape information" << std::endl
<< " Material / mat / m: set tile material information" << std::endl
<< " Special / sp: set special tile information" << std::endl
<< " Variant / var / v: set variant tile information" << std::endl
<< " Designated / d: set designated flag" << std::endl
<< " Hidden / h: set hidden flag" << std::endl
<< " Light / l: set light flag" << std::endl
<< " Subterranean / st: set subterranean flag" << std::endl
<< " Skyview / sv: set skyview flag" << std::endl
<< "See help [option] for more information" << std::endl;
}
else if (option == "shape" || option == "s" ||option == "sh")
{
out << "Available shapes:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_shape,i)
{
out << " " << ENUM_KEY_STR(tiletype_shape,i) << std::endl;
}
}
else if (option == "material"|| option == "mat" ||option == "m")
{
out << "Available materials:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_material,i)
{
out << " " << ENUM_KEY_STR(tiletype_material,i) << std::endl;
}
}
else if (option == "special" || option == "sp")
{
out << "Available specials:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_special,i)
{
out << " " << ENUM_KEY_STR(tiletype_special,i) << std::endl;
}
}
else if (option == "variant" || option == "var" || option == "v")
{
out << "Available variants:" << std::endl
<< " ANY" << std::endl;
FOR_ENUM_ITEMS(tiletype_variant,i)
{
out << " " << ENUM_KEY_STR(tiletype_variant,i) << std::endl;
}
}
else if (option == "designated" || option == "d")
{
out << "Available designated flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "hidden" || option == "h")
{
out << "Available hidden flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "light" || option == "l")
{
out << "Available light flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "subterranean" || option == "st")
{
out << "Available subterranean flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
else if (option == "skyview" || option == "sv")
{
out << "Available skyview flags:" << std::endl
<< " ANY, 0, 1" << std::endl;
}
}
CommandHistory tiletypes_hist;
command_result df_tiletypes (color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("tiletypes");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
tiletypes_hist.load("tiletypes.history");
commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
tiletypes_hist.save("tiletypes.history");
return CR_OK;
}
command_result df_tiletypes (color_ostream &out, vector <string> & parameters)
{
uint32_t x_max = 0, y_max = 0, z_max = 0;
int32_t x = 0, y = 0, z = 0;
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out.print("This tool allows painting tiles types with a brush, using an optional filter.\n"
"The tool is interactive, similarly to the liquids tool.\n"
"Further help is available inside.\n"
);
return CR_OK; return CR_OK;
} }
}
if(!out.is_console())
return CR_FAILURE;
Console &con = static_cast<Console&>(out);
TileType filter, paint;
Brush *brush = new RectangleBrush(1,1);
bool end = false;
std::string brushname = "point";
int width = 1, height = 1, z_levels = 1;
con << "Welcome to the tiletype tool.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl;
con.printerr("THIS TOOL CAN BE DANGEROUS. YOU'VE BEEN WARNED.\n");
while (!end)
{
con << "Filter: " << filter << std::endl
<< "Paint: " << paint << std::endl
<< "Brush: " << brushname << std::endl;
std::string input = "";
std::string command = "";
std::string option = "";
std::string value = "";
con.lineedit("tiletypes> ",input,tiletypes_hist);
tiletypes_hist.add(input);
std::istringstream ss(input);
ss >> command >> option >> value;
tolower(command);
tolower(option);
if (command == "help" || command == "?")
{
help(con,option);
}
else if (command == "quit" || command == "q")
{
end = true;
}
else if (command == "filter" || command == "f")
{
processTileType(filter, option, value);
}
else if (command == "paint" || (command == "p" && !option.empty()))
{
processTileType(paint, option, value);
}
else if (command == "point" || command == "p")
{
delete brush;
brushname = "point";
brush = new RectangleBrush(1,1);
}
else if (command == "range" || command == "r")
{
std::stringstream ss;
CommandHistory hist;
ss << "Set range width <" << width << "> ";
con.lineedit(ss.str(),command,hist);
width = command == "" ? width : toint(command);
if (width < 1) width = 1;
ss.str("");
ss << "Set range height <" << height << "> ";
con.lineedit(ss.str(),command,hist);
height = command == "" ? height : toint(command);
if (height < 1) height = 1;
ss.str("");
ss << "Set range z-levels <" << z_levels << "> ";
con.lineedit(ss.str(),command,hist);
z_levels = command == "" ? z_levels : toint(command);
if (z_levels < 1) z_levels = 1;
delete brush;
if (width == 1 && height == 1 && z_levels == 1)
{
brushname = "point";
}
else
{
brushname = "range";
}
brush = new RectangleBrush(width, height, z_levels, 0, 0, 0);
}
else if (command == "block")
{
delete brush;
brushname = "block";
brush = new BlockBrush();
}
else if (command == "column")
{
delete brush;
brushname = "column";
brush = new ColumnBrush();
}
else if (command.empty())
{
if (paint.empty())
{
con.printerr("Set the paint first.\n");
continue;
}
CoreSuspender suspend; CoreSuspender suspend;
uint32_t x_max = 0, y_max = 0, z_max = 0;
int32_t x = 0, y = 0, z = 0;
if (!Maps::IsValid()) if (!Maps::IsValid())
{ {
con.printerr("Map is not available!\n"); out.printerr("Map is not available!\n");
return CR_FAILURE; return CR_FAILURE;
} }
Maps::getSize(x_max, y_max, z_max); Maps::getSize(x_max, y_max, z_max);
if (!Gui::getCursorCoords(x,y,z)) if (!Gui::getCursorCoords(x,y,z))
{ {
con.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n"); out.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n");
return CR_FAILURE; return CR_FAILURE;
} }
con.print("Cursor coords: (%d, %d, %d)\n",x,y,z); out.print("Cursor coords: (%d, %d, %d)\n", x, y, z);
DFHack::DFCoord cursor(x,y,z); DFHack::DFCoord cursor(x,y,z);
MapExtras::MapCache map; MapExtras::MapCache map;
coord_vec all_tiles = brush->points(map, cursor); coord_vec all_tiles = brush->points(map, cursor);
con.print("working...\n"); out.print("working...\n");
for (coord_vec::iterator iter = all_tiles.begin(); iter != all_tiles.end(); ++iter) for (coord_vec::iterator iter = all_tiles.begin(); iter != all_tiles.end(); ++iter)
{ {
@ -660,7 +626,7 @@ command_result df_tiletypes (color_ostream &out, vector <string> & parameters)
|| (filter.dig > -1 && (filter.dig != 0) != (des.bits.dig != tile_dig_designation::No)) || (filter.dig > -1 && (filter.dig != 0) != (des.bits.dig != tile_dig_designation::No))
) )
{ {
continue; return CR_OK;
} }
df::tiletype_shape shape = paint.shape; df::tiletype_shape shape = paint.shape;
@ -706,13 +672,15 @@ command_result df_tiletypes (color_ostream &out, vector <string> & parameters)
*/ */
// Remove direction from directionless tiles // Remove direction from directionless tiles
DFHack::TileDirection direction = tileDirection(source); DFHack::TileDirection direction = tileDirection(source);
if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH))) { if (!(material == tiletype_material::RIVER || shape == tiletype_shape::BROOK_BED || shape == tiletype_shape::WALL && (material == tiletype_material::CONSTRUCTION || special == tiletype_special::SMOOTH)))
{
direction.whole = 0; direction.whole = 0;
} }
df::tiletype type = DFHack::findTileType(shape, material, variant, special, direction); df::tiletype type = DFHack::findTileType(shape, material, variant, special, direction);
// hack for empty space // hack for empty space
if (shape == tiletype_shape::EMPTY && material == tiletype_material::AIR && variant == tiletype_variant::VAR_1 && special == tiletype_special::NORMAL && direction.whole == 0) { if (shape == tiletype_shape::EMPTY && material == tiletype_material::AIR && variant == tiletype_variant::VAR_1 && special == tiletype_special::NORMAL && direction.whole == 0)
{
type = tiletype::OpenSpace; type = tiletype::OpenSpace;
} }
// make sure it's not invalid // make sure it's not invalid
@ -756,13 +724,218 @@ command_result df_tiletypes (color_ostream &out, vector <string> & parameters)
if (map.WriteAll()) if (map.WriteAll())
{ {
con.print("OK\n"); out.print("OK\n");
} }
else else
{ {
con.printerr("Something failed horribly! RUN!\n"); out.printerr("Something failed horribly! RUN!\n");
return CR_FAILURE;
} }
}
command_result processCommand(color_ostream &out, std::vector<std::string> &commands, int start, int end, bool & endLoop, bool hasConsole = false)
{
if (commands.size() == start)
{
return executePaintJob(out);
} }
std::ostringstream ss_o;
int loc = start;
std::string command = commands[loc++];
tolower(command);
if (command == "help" || command == "?")
{
help(out, commands, loc, end);
}
else if (command == "quit" || command == "q")
{
endLoop = true;
}
else if (command == "filter" || command == "f")
{
processTileType(out, filter, commands, loc, end);
}
else if (command == "paint" || (command == "p" && commands.size() > 1))
{
processTileType(out, paint, commands, loc, end);
}
else if (command == "point" || command == "p")
{
delete brush;
brush = new RectangleBrush(1,1);
}
else if (command == "range" || command == "r")
{
int width = 0, height = 0, z_levels = 0;
if (commands.size() > loc + 1)
{
width = toint(commands[loc++]);
height = toint(commands[loc++]);
if (commands.size() > loc) {
z_levels = toint(commands[loc++]);
}
}
if (width < 1 || height < 1) {
if (hasConsole) {
Console &con = static_cast<Console&>(out);
CommandHistory hist;
ss_o << "Set range width <" << width << "> ";
con.lineedit(ss_o.str(),command,hist);
width = command == "" ? width : toint(command);
ss_o.str("");
ss_o << "Set range height <" << height << "> ";
con.lineedit(ss_o.str(),command,hist);
height = command == "" ? height : toint(command);
ss_o.str("");
ss_o << "Set range z-levels <" << z_levels << "> ";
con.lineedit(ss_o.str(),command,hist);
z_levels = command == "" ? z_levels : toint(command);
} else {
return CR_WRONG_USAGE;
}
}
if (width < 1) width = 1;
if (height < 1) height = 1;
if (z_levels < 1) z_levels = 1;
delete brush;
brush = new RectangleBrush(width, height, z_levels, 0, 0, 0);
}
else if (command == "block")
{
delete brush;
brush = new BlockBrush();
} }
else if (command == "column")
{
delete brush;
brush = new ColumnBrush();
}
else if (command == "run" || command.empty())
{
executePaintJob(out);
}
return CR_OK;
}
command_result df_tiletypes (color_ostream &out_, vector <string> & parameters)
{
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out_.print("This tool allows painting tiles types with a brush, using an optional filter.\n"
"The tool is interactive, similarly to the liquids tool.\n"
"Further help is available inside.\n"
);
return CR_OK;
}
}
if(!out_.is_console())
return CR_FAILURE;
Console &out = static_cast<Console&>(out_);
std::vector<std::string> commands;
bool end = false;
out << "Welcome to the tiletype tool.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl;
out.printerr("THIS TOOL CAN BE DANGEROUS. YOU'VE BEEN WARNED.\n");
while (!end)
{
printState(out);
std::string input = "";
if (out.lineedit("tiletypes> ",input,tiletypes_hist) == -1)
return CR_FAILURE;
tiletypes_hist.add(input);
commands.clear();
Core::cheap_tokenise(input, commands);
command_result ret = processCommand(out, commands, 0, commands.size(), end, true);
if (ret != CR_OK)
{
return ret;
}
}
return CR_OK;
}
command_result df_tiletypes_command (color_ostream &out, vector <string> & parameters)
{
bool dummy;
int start = 0, end = 0;
parameters.push_back(";");
for (size_t i = 0; i < parameters.size();i++)
{
if (parameters[i] == ";") {
command_result rv = processCommand(out, parameters, start, end, dummy);
if (rv != CR_OK) {
return rv;
}
end++;
start = end;
} else {
end++;
}
}
return CR_OK;
}
command_result df_tiletypes_here (color_ostream &out, vector <string> & parameters)
{
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out << "This command is supposed to be mapped to a hotkey." << endl;
out << "It will use the current/last parameters set in tiletypes (including brush settings!)." << endl;
return CR_OK; return CR_OK;
}
}
out.print("Run tiletypes-here with these parameters: ");
printState(out);
return executePaintJob(out);
}
command_result df_tiletypes_here_point (color_ostream &out, vector <string> & parameters)
{
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out << "This command is supposed to be mapped to a hotkey." << endl;
out << "It will use the current/last parameters set in tiletypes (except with a point brush)." << endl;
return CR_OK;
}
}
Brush *old = brush;
brush = new RectangleBrush(1, 1);
out.print("Run tiletypes-here with these parameters: ");
printState(out);
command_result rv = executePaintJob(out);
delete brush;
brush = old;
return rv;
} }

@ -56,10 +56,17 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Note that this is very dirty and possibly dangerous!\n" " Note that this is very dirty and possibly dangerous!\n"
" Most probably does not have the positive effect of a proper burial.\n" " Most probably does not have the positive effect of a proper burial.\n"
" tweak fixmigrant\n" " tweak fixmigrant\n"
" Forces the selected unit to become a member or your fortress.\n" " Remove the resident/merchant flag from the selected unit.\n"
" Intended to fix bugged migrants and merchants who stay at the map edge.\n" " Intended to fix bugged migrants/traders who stay at the\n"
" Only works for units of your own race. Can be used for stealing caravan\n" " map edge and don't enter your fort. Only works for\n"
" traders and guards, but might result into weirdness during trading.\n" " dwarves (or generally the player's race in modded games).\n"
" tweak makeown\n"
" Force selected unit to become a member of your fort.\n"
" Can be abused to grab caravan merchants and escorts, even if\n"
" they don't belong to the player's race. Foreign sentients\n"
" (humans, elves) can be put to work, but you can't assign rooms\n"
" to them and they don't show up in DwarfTherapist because the\n"
" game treats them like pets.\n"
)); ));
return CR_OK; return CR_OK;
} }