diff --git a/LICENSE.rst b/LICENSE.rst index f1e07d111..48bcfbe95 100644 --- a/LICENSE.rst +++ b/LICENSE.rst @@ -34,6 +34,7 @@ tinythread_ Zlib \(c\) 2010, Marcus Geelnard tinyxml_ Zlib \(c\) 2000-2006, Lee Thomason UTF-8-decoder_ MIT \(c\) 2008-2010, Bjoern Hoehrmann xlsxio_ MIT \(c\) 2016-2020, Brecht Sanders +alt-getopt_ MIT \(c\) 2009 Aleksey Cheusov =============== ============= ================================================= .. _DFHack: https://github.com/DFHack/dfhack @@ -52,6 +53,7 @@ xlsxio_ MIT \(c\) 2016-2020, Brecht Sanders .. _tinyxml: http://www.sourceforge.net/projects/tinyxml .. _UTF-8-decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa .. _xlsxio: https://github.com/brechtsanders/xlsxio +.. _alt-getopt: https://github.com/LuaDist/alt-getopt .. _CC-BY-SA: http://creativecommons.org/licenses/by/3.0/deed.en_US diff --git a/docs/Lua API.rst b/docs/Lua API.rst index c03911d7e..81c662728 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -833,6 +833,9 @@ can be omitted. * ``dfhack.getCompiledDFVersion()`` * ``dfhack.getGitDescription()`` * ``dfhack.getGitCommit()`` +* ``dfhack.getGitXmlCommit()`` +* ``dfhack.getGitXmlExpectedCommit()`` +* ``dfhack.gitXmlMatch()`` * ``dfhack.isRelease()`` Return information about the DFHack build in use. @@ -899,6 +902,28 @@ can be omitted. Note that the returned string may be longer than the input string. For example, ``ä`` is replaced with ``a``, and ``æ`` is replaced with ``ae``. +* ``dfhack.run_command(command[, ...])`` + + Run an arbitrary DFHack command, with the core suspended, and send output to + the DFHack console. The command can be passed as a table, multiple string + arguments, or a single string argument (not recommended - in this case, the + usual DFHack console tokenization is used). + + A ``command_result`` constant starting with ``CR_`` is returned, where ``CR_OK`` + indicates success. + + The following examples are equivalent:: + + dfhack.run_command({'ls', '-a'}) + dfhack.run_command('ls', '-a') + dfhack.run_command('ls -a') -- not recommended + +* ``dfhack.run_command_silent(command[, ...])`` + + Similar to ``run_command()``, but instead of printing to the console, + returns an ``output, command_result`` pair. ``output`` is a single string - + see ``dfhack.internal.runCommand()`` to obtain colors as well. + Gui module ---------- @@ -2235,11 +2260,6 @@ Internal API These functions are intended for the use by dfhack developers, and are only documented here for completeness: -* ``dfhack.internal.scripts`` - - The table used by ``dfhack.run_script()`` to give every script its own - global environment, persistent between calls to the script. - * ``dfhack.internal.getPE()`` Returns the PE timestamp of the DF executable (only on Windows) @@ -2354,6 +2374,21 @@ and are only documented here for completeness: This requires an extension to be specified (``.lua`` or ``.rb``) - use ``dfhack.findScript()`` to include the ``.lua`` extension automatically. +* ``dfhack.internal.runCommand(command[, use_console])`` + + Runs a DFHack command with the core suspended. Used internally by the + ``dfhack.run_command()`` family of functions. + + - ``command``: either a table of strings or a single string which is parsed by + the default console tokenization strategy (not recommended) + - ``use_console``: if true, output is sent directly to the DFHack console + + Returns a table with a ``status`` key set to a ``command_result`` constant + (``status = CR_OK`` indicates success). Additionally, if ``use_console`` is + not true, enumerated table entries of the form ``{color, text}`` are included, + e.g. ``result[1][0]`` is the color of the first piece of text printed (a + ``COLOR_`` constant). These entries can be iterated over with ``ipairs()``. + * ``dfhack.internal.md5(string)`` Returns the MD5 hash of the given string. @@ -2513,6 +2548,12 @@ environment by the mandatory init file dfhack.lua: SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED, SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED +* Command result constants (equivalent to ``command_result`` in C++), used by + ``dfhack.run_command()`` and related functions: + + CR_OK, CR_LINK_FAILURE, CR_NEEDS_CONSOLE, CR_NOT_IMPLEMENTED, CR_FAILURE, + CR_WRONG_USAGE, CR_NOT_FOUND + * Functions already described above safecall, qerror, mkmodule, reload diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 544a40844..54bb5213e 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -765,29 +765,32 @@ and displayed with:: The available settings are: -+----------------+---------+---------------------------------------+ -| Setting | Default | Description | -+================+=========+=======================================+ -| blocks | true | Allow blocks, boulders, logs, or bars | -+----------------+---------+ to be matched for generic "building | -| boulders | true | material" items | -+----------------+---------+ | -| logs | true | | -+----------------+---------+ | -| bars | false | | -+----------------+---------+---------------------------------------+ -| quickfort_mode | false | Enable compatibility mode for the | -| | | legacy Python Quickfort (not required | -| | | for DFHack quickfort) | -+----------------+---------+---------------------------------------+ ++----------------+---------+-----------+---------------------------------------+ +| Setting | Default | Persisted | Description | ++================+=========+===========+=======================================+ +| all_enabled | false | no | Enable planning mode for all building | +| | | | types. | ++----------------+---------+-----------+---------------------------------------+ +| blocks | true | yes | Allow blocks, boulders, logs, or bars | ++----------------+---------+ | to be matched for generic "building | +| boulders | true | | material" items | ++----------------+---------+ | | +| logs | true | | | ++----------------+---------+ | | +| bars | false | | | ++----------------+---------+-----------+---------------------------------------+ +| quickfort_mode | false | no | Enable compatibility mode for the | +| | | | legacy Python Quickfort (not required | +| | | | for DFHack quickfort) | ++----------------+---------+-----------+---------------------------------------+ For example, to ensure you only use blocks when a "building material" item is required, you could add this to your ``onMapLoad.init`` file:: on-new-fortress buildingplan set boulders false; buildingplan set logs false -You only need to set the settings for new fortresses since your current filter settings -are saved with your game. +Persisted settings (i.e. ``blocks``, ``boulders``, ``logs``, and ``bars``) are saved with +your game, so you only need to set them to the values you want once. .. _confirm: diff --git a/docs/changelog.txt b/docs/changelog.txt index 21065a9d4..614f6f165 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,13 +36,23 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `embark-assistant`: fixed order of factors when calculating min temperature - `embark-assistant`: improved performance of surveying +- `quickfort`: creating zones no longer causes eventual crashes +- `search`: fixed crash when searching the ``k`` sidebar and navigating to another tile with certain keys, like ``<`` or ``>`` +- `stockflow`: fixed ``j`` character being intercepted when naming stockpiles ## Misc Improvements - `buildingplan`: set global settings from the ``DFHack#`` prompt: e.g. ``buildingplan set boulders false`` - -## Misc Improvements +- `buildingplan`: add 'enable all' option for buildingplan (so you don't have to enable all building types individually). this setting is not persisted (just like quickfort_mode is not persisted), but it can be set from onMapLoad.init +- `buildingplan`: modified ``Planning Mode`` status in the UI to show whether we're in quickfort mode, enable all mode, or whether just the building type is enabled. - `quickfort`: Dreamfort blueprint set improvements: add a streamlined checklist for all required dreamfort commands and give names to stockpiles, levers, bridges, and zones +## Lua +- ``dfhack.run_command()``: changed to interface directly with the console when possible, which allows interactive commands and commands that detect the console encoding to work properly +- ``processArgsGetopt()`` added to utils.lua, providing a callback interface for parameter parsing and getopt-like flexibility for parameter ordering and combination (see docs in ``library/lua/utils.lua`` and ``library/lua/3rdparty/alt_getopt.lua`` for details). + +## Documentation +- Added documentation for Lua's ``dfhack.run_command()`` and variants + # 0.47.04-r4 ## Fixes @@ -1175,4 +1185,3 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - The ``ui_menu_width`` global is now a 2-byte array; the second item is the former ``ui_area_map_width`` global, which is now removed - The former ``announcements`` global is now a field in ``d_init`` - ``world`` fields formerly beginning with ``job_`` are now fields of ``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list`` - diff --git a/library/Console-posix.cpp b/library/Console-posix.cpp index 595f50e97..3c8645ebf 100644 --- a/library/Console-posix.cpp +++ b/library/Console-posix.cpp @@ -405,6 +405,8 @@ namespace DFHack /// A simple line edit (raw mode) int lineedit(const std::string& prompt, std::string& output, recursive_mutex * lock, CommandHistory & ch) { + if(state == con_lineedit) + return Console::FAILURE; output.clear(); reset_color(); this->prompt = prompt; @@ -414,7 +416,9 @@ namespace DFHack fflush(dfout_C); // FIXME: what do we do here??? //SDL_recursive_mutexV(lock); + state = con_lineedit; std::getline(std::cin, output); + state = con_unclaimed; //SDL_recursive_mutexP(lock); return output.size(); } @@ -422,8 +426,6 @@ namespace DFHack { int count; if (enable_raw() == -1) return 0; - if(state == con_lineedit) - return Console::FAILURE; state = con_lineedit; count = prompt_loop(lock,ch); state = con_unclaimed; diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index d339e53a0..515d89911 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -376,6 +376,8 @@ namespace DFHack } int lineedit(const std::string & prompt, std::string & output, recursive_mutex * lock, CommandHistory & ch) { + if(state == con_lineedit) + return Console::FAILURE; output.clear(); reset_color(); int count; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 431ab3ae5..d7f3b269f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2822,13 +2822,29 @@ static int internal_diffscan(lua_State *L) static int internal_runCommand(lua_State *L) { - buffered_color_ostream out; + color_ostream *out = NULL; + std::unique_ptr out_buffer; command_result res; if (lua_gettop(L) == 0) { lua_pushstring(L, ""); } int type_1 = lua_type(L, 1); + bool use_console = lua_toboolean(L, 2); + if (use_console) + { + out = Lua::GetOutput(L); + if (!out) + { + out = &Core::getInstance().getConsole(); + } + } + else + { + out_buffer.reset(new buffered_color_ostream()); + out = out_buffer.get(); + } + if (type_1 == LUA_TTABLE) { std::string command = ""; @@ -2843,13 +2859,13 @@ static int internal_runCommand(lua_State *L) lua_pop(L, 1); // remove value, leave key } CoreSuspender suspend; - res = Core::getInstance().runCommand(out, command, args); + res = Core::getInstance().runCommand(*out, command, args); } else if (type_1 == LUA_TSTRING) { std::string command = lua_tostring(L, 1); CoreSuspender suspend; - res = Core::getInstance().runCommand(out, command); + res = Core::getInstance().runCommand(*out, command); } else { @@ -2857,22 +2873,28 @@ static int internal_runCommand(lua_State *L) lua_pushfstring(L, "Expected table, got %s", lua_typename(L, type_1)); return 2; } - auto fragments = out.fragments(); + lua_newtable(L); lua_pushinteger(L, (int)res); lua_setfield(L, -2, "status"); - int i = 1; - for (auto iter = fragments.begin(); iter != fragments.end(); iter++, i++) + + if (out_buffer) { - int color = iter->first; - std::string output = iter->second; - lua_createtable(L, 2, 0); - lua_pushinteger(L, color); - lua_rawseti(L, -2, 1); - lua_pushstring(L, output.c_str()); - lua_rawseti(L, -2, 2); - lua_rawseti(L, -2, i); + auto fragments = out_buffer->fragments(); + int i = 1; + for (auto iter = fragments.begin(); iter != fragments.end(); iter++, i++) + { + int color = iter->first; + std::string output = iter->second; + lua_createtable(L, 2, 0); + lua_pushinteger(L, color); + lua_rawseti(L, -2, 1); + lua_pushstring(L, output.c_str()); + lua_rawseti(L, -2, 2); + lua_rawseti(L, -2, i); + } } + lua_pushvalue(L, -1); return 1; } diff --git a/library/lua/3rdparty/alt_getopt.lua b/library/lua/3rdparty/alt_getopt.lua new file mode 100644 index 000000000..a356552aa --- /dev/null +++ b/library/lua/3rdparty/alt_getopt.lua @@ -0,0 +1,169 @@ +-- Copyright (c) 2009 Aleksey Cheusov +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +-- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +-- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-- based on https://github.com/LuaDist/alt-getopt/blob/master/alt_getopt.lua +-- MIT licence +-- modified to support aggregation of non-options and to call qerror instead of +-- os.exit() on error. can be used directly or via the utils.processArgs2() +-- wrapper. + +-- sh_opts should be in standard getopt format: a string of letters that +-- represent options, each followed by a colon if that option takes an argument. +-- e.g.: 'ak:hv' has three flags (options with no arguments): 'a', 'h', and 'v' +-- and one option that takes an argument: 'k'. +-- +-- Options passed to the module to parse can be in any of the following formats: +-- -kVALUE, -k VALUE, --key=VALUE, --key VALUE +-- -abcd is equivalent to -a -b -c -d if none of them accept arguments. +-- -abckVALUE and -abck VALUE are also acceptable (where k is the only option +-- in the string that takes a value). + +local _ENV = mkmodule('3rdparty.alt_getopt') + +local function get_opt_map(opts) + local i = 1 + local len = #opts + local options = {} + + for short_opt, accept_arg in opts:gmatch('(%w)(:?)') do + options[short_opt] = #accept_arg + end + + return options +end + +local function err_unknown_opt(opt) + qerror(string.format('Unknown option "-%s%s"', #opt > 1 and '-' or '', opt)) +end + +-- resolve aliases into their canonical forms +local function canonicalize(options, opt) + if not options[opt] then + err_unknown_opt(opt) + end + + while type(options[opt]) == 'string' do + opt = options[opt] + + if not options[opt] then + err_unknown_opt(opt) + end + end + + if type(options[opt]) ~= 'number' then + qerror(string.format( + 'Option "%s" resolves to non-number for has_arg flag', opt)) + end + + return opt +end + +local function has_arg(options, opt) + return options[canonicalize(options, opt)] == 1 +end + +-- returns vectors for opts, optargs, and nonoptions +function get_ordered_opts(args, sh_opts, long_opts) + local optind, count, opts, optargs, nonoptions = 1, 1, {}, {}, {} + + local options = get_opt_map(sh_opts) + for k,v in pairs(long_opts) do + options[k] = v + end + + while optind <= #args do + local a = args[optind] + if a == '--' then + optind = optind + 1 + elseif a:sub(1, 2) == '--' then + local pos = a:find('=', 1, true) + if pos then + local opt = a:sub(3, pos-1) + if not has_arg(options, opt) then + qerror(string.format('Bad usage of option "%s"', a)) + end + opts[count] = opt + optargs[count] = a:sub(pos+1) + else + local opt = a:sub(3) + opts[count] = opt + if has_arg(options, opt) then + if i == #args then + qerror(string.format( + 'Missing value for option "%s"', a)) + end + optargs[count] = args[optind+1] + optind = optind + 1 + end + end + count = count + 1 + elseif a:sub(1, 1) == '-' then + local j + for j=2,#a do + local opt = canonicalize(options, a:sub(j, j)) + if not has_arg(options, opt) then + opts[count] = opt + count = count + 1 + elseif j == #a then + if optind == #args then + qerror(string.format( + 'Missing value for option "-%s"', opt)) + end + opts[count] = opt + optargs[count] = args[optind+1] + optind = optind + 1 + count = count + 1 + else + opts[count] = opt + optargs[count] = a:sub(j+1) + count = count + 1 + break + end + end + else + table.insert(nonoptions, args[optind]) + end + optind = optind + 1 + end + for i=optind,#args do + table.insert(nonoptions, args[i]) + end + return opts, optargs, nonoptions +end + +-- returns a map of options to their optargs (or 1 if the option doesn't take an +-- argument), and a vector for nonoptions +function get_opts(args, sh_opts, long_opts) + local ret = {} + + local opts,optargs,nonoptions = get_ordered_opts(args, sh_opts, long_opts) + for i,v in ipairs(opts) do + if optarg[i] then + ret[v] = optarg[i] + else + ret[v] = 1 + end + end + + return ret, nonoptions +end + +return _ENV diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 73fd651e2..26a23db55 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -735,8 +735,7 @@ function dfhack.script_help(script_name, extension) return help end -local function _run_command(...) - args = {...} +local function _run_command(args, use_console) if type(args[1]) == 'table' then command = args[1] elseif #args > 1 and type(args[2]) == 'table' then @@ -750,11 +749,11 @@ local function _run_command(...) else error('Invalid arguments') end - return internal.runCommand(command) + return internal.runCommand(command, use_console) end function dfhack.run_command_silent(...) - local result = _run_command(...) + local result = _run_command({...}) local output = "" for i, f in pairs(result) do if type(f) == 'table' then @@ -765,14 +764,7 @@ function dfhack.run_command_silent(...) end function dfhack.run_command(...) - local result = _run_command(...) - for i, f in pairs(result) do - if type(f) == 'table' then - dfhack.color(f[1]) - dfhack.print(f[2]) - end - end - dfhack.color(COLOR_RESET) + local result = _run_command({...}, true) return result.status end diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 43dcdcd60..c85560265 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -1,6 +1,7 @@ local _ENV = mkmodule('utils') local df = df +local getopt = require('3rdparty.alt_getopt') -- Comparator function function compare(a,b) @@ -613,6 +614,60 @@ function processArgs(args, validArgs) return result end +-- processes commandline options according to optionActions and returns all +-- argument strings that are not options. Options and non-option strings can +-- appear in any order, and single-letter options that do not take arguments +-- can be combined into a single option string (e.g. '-abc' is the same as +-- '-a -b -c' if options 'a' and 'b' do not take arguments. +-- +-- optionActions is a vector with elements in the following format: +-- {shortOptionName, longOptionAlias, hasArg=boolean, handler=fn} +-- shortOptionName and handler are required. If the option takes an argument, +-- it will be passed to the handler function. +-- longOptionAlias is optional. +-- hasArgument defaults to false. +-- +-- example usage: +-- +-- local filename = nil +-- local open_readonly = false +-- local nonoptions = processArgsGetopt(args, { +-- {'r', handler=function() open_readonly = true end}, +-- {'f', 'filename', hasArg=true, +-- handler=function(optarg) filename = optarg end} +-- }) +-- +-- when args is {'first', '-f', 'fname', 'second'} or, equivalently, +-- {'first', '--filename', 'fname', 'second'} (note the double dash in front of +-- the long option alias), then filename will be fname and nonoptions will +-- contain {'first', 'second'}. +function processArgsGetopt(args, optionActions) + local sh_opts, long_opts = '', {} + local handlers = {} + for _,optionAction in ipairs(optionActions) do + local sh_opt,long_opt = optionAction[1], optionAction[2] + if not sh_opt or type(sh_opt) ~= 'string' or #sh_opt ~= 1 then + error('optionAction missing option letter at index 1') + end + if not optionAction.handler then + error(string.format('handler missing for option "%s"', sh_opt)) + end + sh_opts = sh_opts .. sh_opt + if optionAction.hasArg then sh_opts = sh_opts .. ':' end + handlers[sh_opt] = optionAction.handler + if long_opt then + long_opts[long_opt] = sh_opt + handlers[long_opt] = optionAction.handler + end + end + local opts, optargs, nonoptions = + getopt.get_ordered_opts(args, sh_opts, long_opts) + for i,v in ipairs(opts) do + handlers[v](optargs[i]) + end + return nonoptions +end + function fillTable(table1,table2) for k,v in pairs(table2) do table1[k] = v diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 5a0cd37dc..3dab347d5 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -881,7 +881,7 @@ static void linkRooms(df::building *bld) for (size_t i = 0; i < vec.size(); i++) { auto room = vec[i]; - if (!room->is_room || room->z != bld->z) + if (!room->is_room || room->z != bld->z || room == bld) continue; df::building_extents_type *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1)); diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index e68f9ca4b..6e18219f4 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -27,6 +27,7 @@ REQUIRE_GLOBAL(world); // used in buildingplan library bool show_help = false; bool quickfort_mode = false; +bool all_enabled = false; bool in_dummy_screen = false; std::unordered_map planmode_enabled; @@ -284,7 +285,7 @@ void ViewscreenChooseMaterial::render() //START Viewscreen Hook static bool is_planmode_enabled(BuildingTypeKey key) { - return planmode_enabled[key] || quickfort_mode; + return planmode_enabled[key] || quickfort_mode || all_enabled; } static std::string get_item_label(const BuildingTypeKey &key, int item_idx) @@ -387,6 +388,7 @@ static void show_global_settings_dialog() lua_newtable(L); int ctable = lua_gettop(L); Lua::SetField(L, quickfort_mode, ctable, "quickfort_mode"); + Lua::SetField(L, all_enabled, ctable, "all_enabled"); for (auto & setting : planner.getGlobalSettings()) { @@ -629,7 +631,8 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest show_help = true; } - if (input->count(interface_key::CUSTOM_SHIFT_P)) + if (!quickfort_mode && !all_enabled + && input->count(interface_key::CUSTOM_SHIFT_P)) { planmode_enabled[key] = !planmode_enabled[key]; if (!is_planmode_enabled(key)) @@ -765,8 +768,16 @@ struct buildingplan_place_hook : public df::viewscreen_dwarfmodest OutputString(COLOR_WHITE, x, y, "Use Shift-Keys here", true, left_margin); } - OutputToggleString(x, y, "Planning Mode", interface_key::CUSTOM_SHIFT_P, - planmode_enabled[key], true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); + OutputHotkeyString(x, y, "Planning Mode", interface_key::CUSTOM_SHIFT_P); + OutputString(COLOR_WHITE, x, y, ": "); + if (quickfort_mode) + OutputString(COLOR_YELLOW, x, y, "Quickfort", true, left_margin); + else if (all_enabled) + OutputString(COLOR_YELLOW, x, y, "All", true, left_margin); + else if (planmode_enabled[key]) + OutputString(COLOR_GREEN, x, y, "On", true, left_margin); + else + OutputString(COLOR_GREY, x, y, "Off", true, left_margin); OutputHotkeyString(x, y, "Global Settings", interface_key::CUSTOM_SHIFT_G, true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); @@ -963,6 +974,7 @@ static command_result buildingplan_cmd(color_ostream &out, vector & par // display current settings out.print("active settings:\n"); + out.print(" all_enabled = %s\n", all_enabled ? "true" : "false"); for (auto & setting : planner.getGlobalSettings()) { out.print(" %s = %s\n", setting.first.c_str(), @@ -1108,6 +1120,12 @@ static bool setSetting(std::string name, bool value) { quickfort_mode = value; return true; } + if (name == "all_enabled") + { + debug("setting all_enabled %d -> %d", all_enabled, value); + all_enabled = value; + return true; + } return planner.setGlobalSetting(name, value); } diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h index 0f34b288a..9634d7c48 100644 --- a/plugins/embark-assistant/defs.h +++ b/plugins/embark-assistant/defs.h @@ -68,24 +68,29 @@ namespace embark_assist { Woodland, Heavily_Forested }; - - struct mid_level_tile { + + // only contains those attributes that are being handled during incursion processing + struct mid_level_tile_incursion_base { uint8_t aquifer = Clear_Aquifer_Bits; bool clay = false; bool sand = false; + int8_t soil_depth; + int16_t elevation; + int8_t biome_offset; + tree_levels trees; + uint8_t savagery_level; // 0 - 2 + uint8_t evilness_level; // 0 - 2 + }; + + // contains all attributes (some by inheritance), used for regular survey/matching + struct mid_level_tile : public mid_level_tile_incursion_base { bool flux = false; bool coal = false; - int8_t soil_depth; int8_t offset; - int16_t elevation; river_sizes river_size = river_sizes::None; int16_t river_elevation = 100; int8_t adamantine_level; // -1 = none, 0 .. 3 = cavern 1 .. magma sea. Currently not used beyond present/absent. int8_t magma_level; // -1 = none, 0 .. 3 = cavern 3 .. surface/volcano - int8_t biome_offset; - tree_levels trees; - uint8_t savagery_level; // 0 - 2 - uint8_t evilness_level; // 0 - 2 std::vector metals; std::vector economics; std::vector minerals; @@ -141,10 +146,10 @@ namespace embark_assist { std::vector minerals; std::vector neighbors; // entity_raw indices uint8_t necro_neighbors; - mid_level_tile north_row[16]; - mid_level_tile south_row[16]; - mid_level_tile west_column[16]; - mid_level_tile east_column[16]; + mid_level_tile_incursion_base north_row[16]; + mid_level_tile_incursion_base south_row[16]; + mid_level_tile_incursion_base west_column[16]; + mid_level_tile_incursion_base east_column[16]; uint8_t north_corner_selection[16]; // 0 - 3. For some reason DF stores everything needed for incursion uint8_t west_corner_selection[16]; // detection in 17:th row/colum data in the region details except // this info, so we have to go to neighboring world tiles to fetch it. diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 19e416f1e..3c46c4033 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -68,7 +68,7 @@ namespace embark_assist { void process_embark_incursion(matcher_info *result, embark_assist::defs::world_tile_data *survey_results, - embark_assist::defs::mid_level_tile *mlt, // Note this is a single tile, as opposed to most usages of this variable name. + embark_assist::defs::mid_level_tile_incursion_base *mlt, // Note this is a single tile, as opposed to most usages of this variable name. embark_assist::defs::finders *finder, int16_t elevation, uint16_t x, @@ -2699,7 +2699,7 @@ namespace embark_assist { void merge_incursion_into_world_tile(embark_assist::defs::region_tile_datum* current, embark_assist::defs::region_tile_datum* target_tile, - embark_assist::defs::mid_level_tile* target_mlt) { + embark_assist::defs::mid_level_tile_incursion_base* target_mlt) { df::world_data* world_data = world->world_data; current->aquifer |= target_mlt->aquifer; diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 15db6cb5e..8eb76515f 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -455,7 +455,7 @@ namespace embark_assist { void process_embark_incursion(embark_assist::defs::site_infos *site_info, embark_assist::defs::world_tile_data *survey_results, - embark_assist::defs::mid_level_tile *mlt, // Note this is a single tile, as opposed to most usages of this variable name. + embark_assist::defs::mid_level_tile_incursion_base *mlt, // Note this is a single tile, as opposed to most usages of this variable name. int16_t elevation, uint16_t x, uint16_t y) { @@ -1380,51 +1380,16 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data tile.west_column[i].sand = mlt->at(0).at(i).sand; tile.east_column[i].sand = mlt->at(15).at(i).sand; - tile.north_row[i].flux = mlt->at(i).at(0).flux; // Not used - tile.south_row[i].flux = mlt->at(i).at(15).flux; - tile.west_column[i].flux = mlt->at(0).at(i).flux; - tile.east_column[i].flux = mlt->at(15).at(i).flux; - - tile.north_row[i].coal = mlt->at(i).at(0).coal; // Not used - tile.south_row[i].coal = mlt->at(i).at(15).coal; - tile.west_column[i].coal = mlt->at(0).at(i).coal; - tile.east_column[i].coal = mlt->at(15).at(i).coal; - tile.north_row[i].soil_depth = mlt->at(i).at(0).soil_depth; tile.south_row[i].soil_depth = mlt->at(i).at(15).soil_depth; tile.west_column[i].soil_depth = mlt->at(0).at(i).soil_depth; tile.east_column[i].soil_depth = mlt->at(15).at(i).soil_depth; - tile.north_row[i].offset = mlt->at(i).at(0).offset; // Not used - tile.south_row[i].offset = mlt->at(i).at(15).offset; - tile.west_column[i].offset = mlt->at(0).at(i).offset; - tile.east_column[i].offset = mlt->at(15).at(i).offset; - tile.north_row[i].elevation = mlt->at(i).at(0).elevation; tile.south_row[i].elevation = mlt->at(i).at(15).elevation; tile.west_column[i].elevation = mlt->at(0).at(i).elevation; tile.east_column[i].elevation = mlt->at(15).at(i).elevation; - tile.north_row[i].river_size = mlt->at(i).at(0).river_size; // Not used - tile.south_row[i].river_size = mlt->at(i).at(15).river_size; - tile.west_column[i].river_size = mlt->at(0).at(i).river_size; - tile.east_column[i].river_size = mlt->at(15).at(i).river_size; - - tile.north_row[i].river_elevation = mlt->at(i).at(0).river_elevation; // Not used - tile.south_row[i].river_elevation = mlt->at(i).at(15).river_elevation; - tile.west_column[i].river_elevation = mlt->at(0).at(i).river_elevation; - tile.east_column[i].river_elevation = mlt->at(15).at(i).river_elevation; - - tile.north_row[i].adamantine_level = mlt->at(i).at(0).adamantine_level; // Not used - tile.south_row[i].adamantine_level = mlt->at(i).at(15).adamantine_level; - tile.west_column[i].adamantine_level = mlt->at(0).at(i).adamantine_level; - tile.east_column[i].adamantine_level = mlt->at(15).at(i).adamantine_level; - - tile.north_row[i].magma_level = mlt->at(i).at(0).magma_level; // Not used - tile.south_row[i].magma_level = mlt->at(i).at(15).magma_level; - tile.west_column[i].magma_level = mlt->at(0).at(i).magma_level; - tile.east_column[i].magma_level = mlt->at(15).at(i).magma_level; - tile.north_row[i].biome_offset = mlt->at(i).at(0).biome_offset; tile.south_row[i].biome_offset = mlt->at(i).at(15).biome_offset; tile.west_column[i].biome_offset = mlt->at(0).at(i).biome_offset; @@ -1445,21 +1410,6 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data tile.west_column[i].evilness_level = mlt->at(0).at(i).evilness_level; tile.east_column[i].evilness_level = mlt->at(15).at(i).evilness_level; - tile.north_row[i].metals.resize(0); // Not used - tile.south_row[i].metals.resize(0); - tile.west_column[i].metals.resize(0); - tile.east_column[i].metals.resize(0); - - tile.north_row[i].economics.resize(0); // Not used - tile.south_row[i].economics.resize(0); - tile.west_column[i].economics.resize(0); - tile.east_column[i].economics.resize(0); - - tile.north_row[i].minerals.resize(0); // Not used - tile.south_row[i].minerals.resize(0); - tile.west_column[i].minerals.resize(0); - tile.east_column[i].minerals.resize(0); - tile.north_corner_selection[i] = world_data->region_details[0]->edges.biome_corner[i][0]; tile.west_corner_selection[i] = world_data->region_details[0]->edges.biome_corner[0][i]; tile.north_row_biome_x[i] = world_data->region_details[0]->edges.biome_x[i][0]; diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index f640969b8..c0e69168a 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -229,7 +229,16 @@ end setting is not needed for DFHack quickfort. --]] function GlobalSettings:init() + self.subviews.label:setText{ + self:make_setting_label_token('Enable all', 'CUSTOM_E', 'all_enabled', 12), + self:make_setting_value_token('all_enabled'), '\n', + ' Enables buildingplan for all building types. Use this to avoid having\n', + ' to manually enable buildingplan for each building type that you want\n', + ' to plan. Note that DFHack quickfort will use buildingplan to manage\n', + ' buildings regardless of whether buildingplan is "enabled" for the\n', + ' building type.\n', + '\n', 'Allowed types for generic, fire-safe, and magma-safe building material:\n', self:make_setting_label_token('Blocks', 'CUSTOM_B', 'blocks', 10), self:make_setting_value_token('blocks'), '\n', diff --git a/plugins/search.cpp b/plugins/search.cpp index 27958f6dc..9ecae6460 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -115,13 +115,16 @@ static string get_unit_description(df::unit *unit) return desc; } -static bool cursor_key_pressed (std::set *input) +static bool cursor_key_pressed (std::set *input, bool in_entry_mode) { - // give text input (e.g. "2") priority over cursor keys - for (auto it = input->begin(); it != input->end(); ++it) + if (in_entry_mode) { - if (Screen::keyToChar(*it) != -1) - return false; + // give text input (e.g. "2") priority over cursor keys + for (auto it = input->begin(); it != input->end(); ++it) + { + if (Screen::keyToChar(*it) != -1) + return false; + } } return input->count(df::interface_key::CURSOR_UP) || @@ -249,7 +252,7 @@ public: // ENTER or ESC: leave typing mode end_entry_mode(); } - else if (cursor_key_pressed(input)) + else if (cursor_key_pressed(input, entry_mode)) { // Arrow key pressed. Leave entry mode and allow screen to process key end_entry_mode(); @@ -1953,7 +1956,9 @@ public: end_entry_mode(); return false; } - if (cursor_key_pressed(input)) + bool hotkey_pressed = + input->lower_bound(interface_key::D_HOTKEY1) != input->upper_bound(interface_key::D_HOTKEY16); + if (cursor_key_pressed(input, in_entry_mode()) || hotkey_pressed) { end_entry_mode(); clear_search(); diff --git a/plugins/stockflow.cpp b/plugins/stockflow.cpp index 06635d8c0..22e4abdf9 100644 --- a/plugins/stockflow.cpp +++ b/plugins/stockflow.cpp @@ -266,6 +266,9 @@ struct stockflow_hook : public df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; bool handleInput(set *input) { + if (Gui::inRenameBuilding()) + return false; + building_stockpilest *sp = get_selected_stockpile(); if (!sp) return false; diff --git a/scripts b/scripts index ebe6f5d59..e20fa9f5c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ebe6f5d599bfafb614a791b637413e976c8870a5 +Subproject commit e20fa9f5ca7707377477b3d9c032ec1f3a2e6414