diff --git a/Lua API.html b/Lua API.html
index 4aecf48a0..2a214259c 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -1119,6 +1119,12 @@ can be omitted.
diff --git a/Lua API.rst b/Lua API.rst
index 1fd5147b5..6c769f004 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -812,6 +812,14 @@ can be omitted.
Convert a language_name or only the last name part to string.
+* ``dfhack.df2utf(string)``
+
+ Convert a string from DF's CP437 encoding to UTF-8.
+
+* ``dfhack.utf2df(string)``
+
+ Convert a string from UTF-8 to DF's CP437 encoding.
+
Gui module
----------
diff --git a/NEWS b/NEWS
index e52e76998..854c9353e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,11 @@
DFHack future
- - Is not yet known.
+ Internals:
+ - support for calling a lua function via a protobuf request (demonstrated by dfhack-run --lua).
+
+ New commands:
+ - move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands
+ - 'plant create' - spawn a new shrub under the cursor
Misc improvements:
- digfort: improved csv parsing, add start() comment handling
diff --git a/Readme.rst b/Readme.rst
index a060ac97a..0668fd6c9 100644
--- a/Readme.rst
+++ b/Readme.rst
@@ -693,26 +693,30 @@ Options:
Beware that filling in hollow veins will trigger a demon invasion on top of
your miner when you dig into the region that used to be hollow.
-extirpate
----------
-A tool for getting rid of trees and shrubs. By default, it only kills
-a tree/shrub under the cursor. The plants are turned into ashes instantly.
+plant
+-----
+A tool for creating shrubs, growing, or getting rid of them.
-Options:
+Subcommands:
+ :create: Create a new shrub/sapling.
+ :grow: Make saplings grow into trees.
+ :extirpate: Kills trees and shrubs, turning them into ashes instantly.
+ :immolate: Similar to extirpate, but sets the plants on fire instead. The
+fires can and *will* spread ;)
+
+``create`` creates a new sapling under the cursor. Takes a raw ID as
+argument (e.g. TOWER_CAP). The cursor must be located on a dirt or grass
+floor tile.
+
+``grow`` works on the sapling under the cursor, and turns it into a tree.
+Works on all shrubs of the map if the cursor is hidden.
+``extirpate`` and ``immolate`` work only on the plant under the cursor.
+For mass effects, use one of the additional options:
:shrubs: affect all shrubs on the map
:trees: affect all trees on the map
:all: affect every plant!
-grow
-----
-Makes all saplings present on the map grow into trees (almost) instantly.
-
-immolate
---------
-Very similar to extirpate, but additionally sets the plants on fire. The fires
-can and *will* spread ;)
-
regrass
-------
Regrows grass. Not much to it ;)
diff --git a/library/Core.cpp b/library/Core.cpp
index fad01e4b2..96fd3903f 100644
--- a/library/Core.cpp
+++ b/library/Core.cpp
@@ -811,6 +811,15 @@ bool Core::loadScriptFile(color_ostream &out, string fname, bool silent)
}
}
+// Load dfhack.init in a dedicated thread (non-interactive console mode)
+void fInitthread(void * iodata)
+{
+ IODATA * iod = ((IODATA*) iodata);
+ Core * core = iod->core;
+ color_ostream_proxy out(core->getConsole());
+ core->loadScriptFile(out, "dfhack.init", true);
+}
+
// A thread function... for the interactive console.
void fIOthread(void * iodata)
{
@@ -822,7 +831,7 @@ void fIOthread(void * iodata)
main_history.load("dfhack.history");
Console & con = core->getConsole();
- if(plug_mgr == 0 || core == 0)
+ if (plug_mgr == 0)
{
con.printerr("Something horrible happened in Core's constructor...\n");
return;
@@ -1008,16 +1017,24 @@ bool Core::Init()
IODATA *temp = new IODATA;
temp->core = this;
temp->plug_mgr = plug_mgr;
+
+ HotkeyMutex = new mutex();
+ HotkeyCond = new condition_variable();
+
if (!is_text_mode)
{
cerr << "Starting IO thread.\n";
// create IO thread
thread * IO = new thread(fIOthread, (void *) temp);
}
+ else
+ {
+ cerr << "Starting dfhack.init thread.\n";
+ thread * init = new thread(fInitthread, (void *) temp);
+ }
+
cerr << "Starting DF input capture thread.\n";
// set up hotkey capture
- HotkeyMutex = new mutex();
- HotkeyCond = new condition_variable();
thread * HK = new thread(fHKthread, (void *) temp);
screen_window = new Windows::top_level_window();
screen_window->addChild(new Windows::dfhack_dummy(5,10));
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index bdc0c39e5..c64ad6634 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -1279,6 +1279,9 @@ static std::string getHackPath() { return Core::getInstance().getHackPath(); }
static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); }
static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); }
+static std::string df2utf(std::string s) { return DF2UTF(s); }
+static std::string utf2df(std::string s) { return UTF2DF(s); }
+
static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(getOSType),
WRAP(getDFVersion),
@@ -1288,6 +1291,8 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(isWorldLoaded),
WRAP(isMapLoaded),
WRAPM(Translation, TranslateName),
+ WRAP(df2utf),
+ WRAP(utf2df),
{ NULL, NULL }
};
diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp
index 718d1b181..17f5258e1 100644
--- a/library/RemoteTools.cpp
+++ b/library/RemoteTools.cpp
@@ -55,6 +55,8 @@ POSSIBILITY OF SUCH DAMAGE.
#include "modules/Units.h"
#include "modules/World.h"
+#include "LuaTools.h"
+
#include "DataDefs.h"
#include "df/ui.h"
#include "df/ui_advmode.h"
@@ -656,6 +658,8 @@ CoreService::CoreService() {
addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND);
addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND);
+ addMethod("RunLua", &CoreService::RunLua);
+
// Functions:
addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND);
addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND);
@@ -730,3 +734,85 @@ command_result CoreService::CoreResume(color_ostream &stream, const EmptyMessage
cnt->set_value(--suspend_depth);
return CR_OK;
}
+
+namespace {
+ struct LuaFunctionData {
+ command_result rv;
+ const dfproto::CoreRunLuaRequest *in;
+ StringListMessage *out;
+ };
+}
+
+command_result CoreService::RunLua(color_ostream &stream,
+ const dfproto::CoreRunLuaRequest *in,
+ StringListMessage *out)
+{
+ auto L = Lua::Core::State;
+ LuaFunctionData data = { CR_FAILURE, in, out };
+
+ lua_pushcfunction(L, doRunLuaFunction);
+ lua_pushlightuserdata(L, &data);
+
+ if (!Lua::Core::SafeCall(stream, 1, 0))
+ return CR_FAILURE;
+
+ return data.rv;
+}
+
+int CoreService::doRunLuaFunction(lua_State *L)
+{
+ color_ostream &out = *Lua::GetOutput(L);
+ auto &args = *(LuaFunctionData*)lua_touserdata(L, 1);
+
+ // Verify module name
+ std::string module = args.in->module();
+ size_t len = module.size();
+
+ bool valid = false;
+
+ if (len > 4)
+ {
+ if (module.substr(0,4) == "rpc.")
+ valid = true;
+ else if ((module[len-4] == '.' || module[len-4] == '-') && module.substr(len-3) != "rpc")
+ valid = true;
+ }
+
+ if (!valid)
+ {
+ args.rv = CR_WRONG_USAGE;
+ out.printerr("Only modules named rpc.* or *.rpc or *-rpc may be called.\n");
+ return 0;
+ }
+
+ // Prepare function and arguments
+ lua_settop(L, 0);
+
+ if (!Lua::PushModulePublic(out, L, module.c_str(), args.in->function().c_str())
+ || lua_isnil(L, 1))
+ {
+ args.rv = CR_NOT_FOUND;
+ return 0;
+ }
+
+ luaL_checkstack(L, args.in->arguments_size(), "too many arguments");
+
+ for (int i = 0; i < args.in->arguments_size(); i++)
+ lua_pushstring(L, args.in->arguments(i).c_str());
+
+ // Call
+ lua_call(L, args.in->arguments_size(), LUA_MULTRET);
+
+ // Store results
+ int nresults = lua_gettop(L);
+
+ for (int i = 1; i <= nresults; i++)
+ {
+ size_t len;
+ const char *data = lua_tolstring(L, i, &len);
+ args.out->add_value(std::string(data, len));
+ }
+
+ args.rv = CR_OK;
+ return 0;
+}
diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp
index f79e49a3c..abe34f34d 100644
--- a/library/dfhack-run.cpp
+++ b/library/dfhack-run.cpp
@@ -72,20 +72,54 @@ int main (int argc, char *argv[])
if (!client.connect())
return 2;
- // Call the command
- std::vector
args;
- for (int i = 2; i < argc; i++)
- args.push_back(argv[i]);
+ command_result rv;
- command_result rv = client.run_command(argv[1], args);
-
- if (rv != CR_OK) {
- if (rv == CR_NOT_IMPLEMENTED)
- out.printerr("%s is not a recognized command.\n", argv[1]);
+ if (strcmp(argv[1], "--lua") == 0)
+ {
+ if (argc <= 3)
+ {
+ fprintf(stderr, "Usage: dfhack-run --lua [args...]\n");
+ return 2;
+ }
+
+ RemoteFunction run_call;
+
+ if (!run_call.bind(&client, "RunLua"))
+ {
+ fprintf(stderr, "No RunLua protocol function found.");
+ return 3;
+ }
+
+ run_call.in()->set_module(argv[2]);
+ run_call.in()->set_function(argv[3]);
+ for (int i = 4; i < argc; i++)
+ run_call.in()->add_arguments(argv[i]);
+
+ rv = run_call();
+
+ out.flush();
+
+ if (rv == CR_OK)
+ {
+ for (int i = 0; i < run_call.out()->value_size(); i++)
+ printf("%s%s", (i>0?"\t":""), run_call.out()->value(i).c_str());
+ printf("\n");
+ }
+ }
+ else
+ {
+ // Call the command
+ std::vector args;
+ for (int i = 2; i < argc; i++)
+ args.push_back(argv[i]);
- return 1;
+ rv = client.run_command(argv[1], args);
}
out.flush();
+
+ if (rv != CR_OK)
+ return 1;
+
return 0;
}
diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h
index e70cefc00..ead1c0aa1 100644
--- a/library/include/RemoteTools.h
+++ b/library/include/RemoteTools.h
@@ -48,6 +48,8 @@ namespace DFHack
DFHACK_EXPORT void strVectorToRepeatedField(RepeatedPtrField *pf,
const std::vector &vec);
+ using dfproto::StringListMessage;
+
/**
* Represent bitfield bits as a repeated string field.
*/
@@ -131,6 +133,8 @@ namespace DFHack
class CoreService : public RPCService {
int suspend_depth;
+
+ static int doRunLuaFunction(lua_State *L);
public:
CoreService();
~CoreService();
@@ -144,5 +148,9 @@ namespace DFHack
// For batching
command_result CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt);
command_result CoreResume(color_ostream &stream, const EmptyMessage*, IntMessage *cnt);
+
+ command_result RunLua(color_ostream &stream,
+ const dfproto::CoreRunLuaRequest *in,
+ StringListMessage *out);
};
}
diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp
index 6ca696485..db3b4cdd9 100644
--- a/library/modules/MapCache.cpp
+++ b/library/modules/MapCache.cpp
@@ -1143,7 +1143,7 @@ MapExtras::MapCache::MapCache()
memset(biomes[i].layer_stone, -1, sizeof(biomes[i].layer_stone));
- for (size_t j = 0; j < std::min(BiomeInfo::MAX_LAYERS,layer_mats[i].size()); j++)
+ for (size_t j = 0; j < std::min(BiomeInfo::MAX_LAYERS,layer_mats[i].size()); j++)
{
biomes[i].layer_stone[j] = layer_mats[i][j];
diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto
index 92d7c48d9..e53078875 100644
--- a/library/proto/CoreProtocol.proto
+++ b/library/proto/CoreProtocol.proto
@@ -81,3 +81,10 @@ message CoreRunCommandRequest {
// RPC CoreSuspend : EmptyMessage -> IntMessage
// RPC CoreResume : EmptyMessage -> IntMessage
+
+// RPC RunLua : CoreRunLuaRequest -> StringListMessage
+message CoreRunLuaRequest {
+ required string module = 1;
+ required string function = 2;
+ repeated string arguments = 3;
+}
diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp
index 97527c0ed..4bfed38d9 100644
--- a/plugins/eventful.cpp
+++ b/plugins/eventful.cpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include
diff --git a/plugins/plants.cpp b/plugins/plants.cpp
index c0cd427a9..6eec4c8c1 100644
--- a/plugins/plants.cpp
+++ b/plugins/plants.cpp
@@ -65,10 +65,8 @@ static bool getoptions( vector & parameters, bool & shrubs, bool & tree
* And he cursed the plants and trees for their bloodless wood, turning them into ash and smoldering ruin.
* Armok was pleased and great temples were built by the dwarves, for they shared his hatred for trees and plants.
*/
-static command_result immolations (color_ostream &out, do_what what, bool shrubs, bool trees, bool help)
+static command_result immolations (color_ostream &out, do_what what, bool shrubs, bool trees)
{
- if(help)
- return CR_WRONG_USAGE;
CoreSuspender suspend;
if (!Maps::IsValid())
{
@@ -132,32 +130,32 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs
return CR_OK;
}
-command_result df_immolate (color_ostream &out, vector & parameters)
+command_result df_immolate (color_ostream &out, vector & parameters, do_what what)
{
bool shrubs = false, trees = false, help = false;
- if(getoptions(parameters,shrubs,trees,help))
+ if (getoptions(parameters, shrubs, trees, help) && !help)
{
- return immolations(out,do_immolate,shrubs,trees,help);
+ return immolations(out, what, shrubs, trees);
}
- else
- {
- out.printerr("Invalid parameter!\n");
- return CR_WRONG_USAGE;
- }
-}
-command_result df_extirpate (color_ostream &out, vector & parameters)
-{
- bool shrubs = false, trees = false, help = false;
- if(getoptions(parameters,shrubs,trees,help))
- {
- return immolations(out,do_extirpate,shrubs,trees,help);
- }
+ string mode;
+ if (what == do_immolate)
+ mode = "Set plants on fire";
else
- {
+ mode = "Kill plants";
+
+ if (!help)
out.printerr("Invalid parameter!\n");
- return CR_WRONG_USAGE;
- }
+
+ out << "Usage:\n" <<
+ mode << " (under cursor, 'shrubs', 'trees' or 'all').\n"
+ "Without any options, this command acts on the plant under the cursor.\n"
+ "Options:\n"
+ "shrubs - affect all shrubs\n"
+ "trees - affect all trees\n"
+ "all - affect all plants\n";
+
+ return CR_OK;
}
command_result df_grow (color_ostream &out, vector & parameters)
@@ -165,8 +163,14 @@ command_result df_grow (color_ostream &out, vector & parameters)
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
- return CR_WRONG_USAGE;
+ {
+ out << "Usage:\n"
+ "This command turns all living saplings on the map into full-grown trees.\n"
+ "With active cursor, work on the targetted one only.\n";
+ return CR_OK;
+ }
}
+
CoreSuspender suspend;
if (!Maps::IsValid())
@@ -217,7 +221,13 @@ command_result df_grow (color_ostream &out, vector & parameters)
command_result df_createplant (color_ostream &out, vector & parameters)
{
if ((parameters.size() != 1) || (parameters[0] == "help" || parameters[0] == "?"))
- return CR_WRONG_USAGE;
+ {
+ out << "Usage:\n"
+ "Create a new plant at the cursor.\n"
+ "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n"
+ "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n";
+ return CR_OK;
+ }
CoreSuspender suspend;
@@ -308,29 +318,43 @@ command_result df_createplant (color_ostream &out, vector & parameters)
return CR_OK;
}
+command_result df_plant (color_ostream &out, vector & parameters)
+{
+ if (parameters.size() >= 1)
+ {
+ if (parameters[0] == "grow") {
+ parameters.erase(parameters.begin());
+ return df_grow(out, parameters);
+ } else
+ if (parameters[0] == "immolate") {
+ parameters.erase(parameters.begin());
+ return df_immolate(out, parameters, do_immolate);
+ } else
+ if (parameters[0] == "extirpate") {
+ parameters.erase(parameters.begin());
+ return df_immolate(out, parameters, do_extirpate);
+ } else
+ if (parameters[0] == "create") {
+ parameters.erase(parameters.begin());
+ return df_createplant(out, parameters);
+ }
+ }
+ return CR_WRONG_USAGE;
+}
+
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands)
{
- commands.push_back(PluginCommand("grow", "Grows saplings into trees (with active cursor, only the targetted one).", df_grow, false,
- "This command turns all living saplings on the map into full-grown trees.\n"));
- commands.push_back(PluginCommand("immolate", "Set plants on fire (under cursor, 'shrubs', 'trees' or 'all').", df_immolate, false,
- "Without any options, this command burns a plant under the cursor.\n"
- "Options:\n"
- "shrubs - affect all shrubs\n"
- "trees - affect all trees\n"
- "all - affect all plants\n"));
- commands.push_back(PluginCommand("extirpate", "Kill plants (same mechanics as immolate).", df_extirpate, false,
- "Without any options, this command destroys a plant under the cursor.\n"
- "Options:\n"
- "shrubs - affect all shrubs\n"
- "trees - affect all trees\n"
- "all - affect all plants\n"));
- commands.push_back(PluginCommand("createplant", "Create a new plant at the cursor.", df_createplant, false,
- "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n"
- "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n"));
+ commands.push_back(PluginCommand("plant", "Plant creation and removal.", df_plant, false,
+ "Command to create, grow or remove plants on the map. For more details, check the subcommand help :\n"
+ "plant grow help - Grows saplings into trees.\n"
+ "plant immolate help - Set plants on fire.\n"
+ "plant extirpate help - Kill plants.\n"
+ "plant create help - Create a new plant.\n"));
+
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
-}
\ No newline at end of file
+}
diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp
index 57bfd9658..2a6d6b17c 100644
--- a/plugins/showmood.cpp
+++ b/plugins/showmood.cpp
@@ -169,7 +169,13 @@ command_result df_showmood (color_ostream &out, vector & parameters)
// total amount of stuff fetched so far
int count_got = 0;
for (size_t i = 0; i < job->items.size(); i++)
- count_got += 1; // XXX thread may need job->items[i]->item->getTotalDimension()
+ {
+ df::item_type type = job->job_items[i]->item_type;
+ if (type == item_type::BAR || type == item_type::CLOTH)
+ count_got += job->items[i]->item->getTotalDimension();
+ else
+ count_got += 1;
+ }
for (size_t i = 0; i < job->job_items.size(); i++)
{
diff --git a/plugins/zone.cpp b/plugins/zone.cpp
index 0271cb75f..4def386b7 100644
--- a/plugins/zone.cpp
+++ b/plugins/zone.cpp
@@ -3839,12 +3839,12 @@ void unbutcherRace(int race)
static bool autobutcher_isEnabled() { return enable_autobutcher; }
static bool autowatch_isEnabled() { return enable_autobutcher_autowatch; }
-static size_t autobutcher_getSleep(color_ostream &out)
+static unsigned autobutcher_getSleep(color_ostream &out)
{
return sleep_autobutcher;
}
-static void autobutcher_setSleep(color_ostream &out, size_t ticks)
+static void autobutcher_setSleep(color_ostream &out, unsigned ticks)
{
sleep_autobutcher = ticks;
if(config_autobutcher.isValid())
@@ -3892,7 +3892,7 @@ static void autowatch_setEnabled(color_ostream &out, bool enable)
// set all data for a watchlist race in one go
// if race is not already on watchlist it will be added
// params: (id, fk, mk, fa, ma, watched)
-static void autobutcher_setWatchListRace(color_ostream &out, size_t id, size_t fk, size_t mk, size_t fa, size_t ma, bool watched)
+static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsigned fk, unsigned mk, unsigned fa, unsigned ma, bool watched)
{
int watched_index = getWatchedIndex(id);
if(watched_index != -1)
@@ -3921,7 +3921,7 @@ static void autobutcher_setWatchListRace(color_ostream &out, size_t id, size_t f
}
// remove entry from watchlist
-static void autobutcher_removeFromWatchList(color_ostream &out, size_t id)
+static void autobutcher_removeFromWatchList(color_ostream &out, unsigned id)
{
int watched_index = getWatchedIndex(id);
if(watched_index != -1)
@@ -3940,7 +3940,7 @@ static void autobutcher_sortWatchList(color_ostream &out)
}
// set default target values for new races
-static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma)
+static void autobutcher_setDefaultTargetNew(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma)
{
default_fk = fk;
default_mk = mk;
@@ -3956,9 +3956,9 @@ static void autobutcher_setDefaultTargetNew(color_ostream &out, size_t fk, size_
}
// set default target values for ALL races (update watchlist and set new default)
-static void autobutcher_setDefaultTargetAll(color_ostream &out, size_t fk, size_t mk, size_t fa, size_t ma)
+static void autobutcher_setDefaultTargetAll(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma)
{
- for(size_t i=0; ifk = fk;
@@ -3970,12 +3970,12 @@ static void autobutcher_setDefaultTargetAll(color_ostream &out, size_t fk, size_
autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma);
}
-static void autobutcher_butcherRace(color_ostream &out, size_t id)
+static void autobutcher_butcherRace(color_ostream &out, unsigned id)
{
butcherRace(id);
}
-static void autobutcher_unbutcherRace(color_ostream &out, size_t id)
+static void autobutcher_unbutcherRace(color_ostream &out, unsigned id)
{
unbutcherRace(id);
}