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.

  • dfhack.TranslateName(name[,in_english,only_last_name])

    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/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); }