diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index efa59812d..1d481edce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.23.1 + rev: 0.23.2 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks diff --git a/CMakeLists.txt b/CMakeLists.txt index bedebfbaa..f4cb3a9f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,8 +8,8 @@ project(dfhack) # set up versioning. set(DF_VERSION "50.09") -set(DFHACK_RELEASE "r1") -set(DFHACK_PRERELEASE FALSE) +set(DFHACK_RELEASE "r2rc2") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_ABI_VERSION 1) @@ -35,9 +35,10 @@ option(REMOVE_SYMBOLS_FROM_DF_STUBS "Remove debug symbols from DF stubs. (Reduce macro(CHECK_GCC compiler_path) execute_process(COMMAND ${compiler_path} -dumpversion OUTPUT_VARIABLE GCC_VERSION_OUT) string(STRIP "${GCC_VERSION_OUT}" GCC_VERSION_OUT) - if(${GCC_VERSION_OUT} VERSION_LESS "4.8") - message(SEND_ERROR "${compiler_path} version ${GCC_VERSION_OUT} cannot be used - use GCC 4.8 or later") - elseif(${GCC_VERSION_OUT} VERSION_GREATER "4.9.9") + if(${GCC_VERSION_OUT} VERSION_LESS "10") + message(SEND_ERROR "${compiler_path} version ${GCC_VERSION_OUT} cannot be used - use GCC 10 or later") + # TODO: this may need to be removed when DF linux actually comes out + # TODO: and we can test # GCC 5 changes ABI name mangling to enable C++11 changes. # This must be disabled to enable linking against DF. # http://developerblog.redhat.com/2015/02/05/gcc5-and-the-c11-abi/ @@ -66,8 +67,8 @@ if(WIN32) endif() endif() -# Ask for C++11 standard from compilers -set(CMAKE_CXX_STANDARD 11) +# Ask for C++-20 standard from compilers +set(CMAKE_CXX_STANDARD 20) # Require the standard support from compilers. set(CMAKE_CXX_STANDARD_REQUIRED ON) # Use only standard c++ to keep code portable @@ -226,9 +227,7 @@ if(UNIX) ## flags for GCC # default to hidden symbols # ensure compatibility with older CPUs - # enable C++11 features add_definitions(-DLINUX_BUILD) - add_definitions(-D_GLIBCXX_USE_C99) set(GCC_COMMON_FLAGS "-fvisibility=hidden -mtune=generic -Wall -Werror") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COMMON_FLAGS}") diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 15ff52488..0756519ef 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -4,11 +4,19 @@ add_subdirectory(lua) add_subdirectory(md5) add_subdirectory(protobuf) +if(UNIX) + set_target_properties(lua PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations -Wno-deprecated-enum-enum-conversion") + set_target_properties(protoc PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations -Wno-restrict") + set_target_properties(protoc-bin PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations -Wno-restrict") + set_target_properties(protobuf-lite PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations -Wno-restrict") + set_target_properties(protobuf PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations -Wno-restrict") +endif() + if(UNIX AND NOT APPLE) # remove this once our MSVC build env has been updated option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" OFF) add_subdirectory(googletest) if(UNIX) - set_target_properties(gtest PROPERTIES COMPILE_FLAGS "-Wno-maybe-uninitialized -Wno-sign-compare") + set_target_properties(gtest PROPERTIES COMPILE_FLAGS "-Wno-maybe-uninitialized -Wno-sign-compare -Wno-restrict") endif() endif() diff --git a/depends/protobuf/google/protobuf/repeated_field.h b/depends/protobuf/google/protobuf/repeated_field.h index aed4ce9f2..637708254 100644 --- a/depends/protobuf/google/protobuf/repeated_field.h +++ b/depends/protobuf/google/protobuf/repeated_field.h @@ -46,6 +46,10 @@ #ifndef GOOGLE_PROTOBUF_REPEATED_FIELD_H__ #define GOOGLE_PROTOBUF_REPEATED_FIELD_H__ +#ifdef __GNUC__ +#pragma GCC system_header +#endif + #include #include #include diff --git a/docs/Core.rst b/docs/Core.rst index 763858b61..ac17401dc 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -377,6 +377,26 @@ Other (non-DFHack-specific) variables that affect DFHack: sensitive), ``DF2CONSOLE()`` will produce UTF-8-encoded text. Note that this should be the case in most UTF-8-capable \*nix terminal emulators already. +Core preferences +================ + +There are a few settings that can be changed dynamically via +`gui/control-panel` to affect runtime behavior. You can also toggle these from +the commandline using the `lua` command, e.g. +``lua dfhack.HIDE_ARMOK_TOOLS=true`` or by editing the generated +``dfhack-config/init/dfhack.control-panel-preferences.init`` file and +restarting DF. + +- ``dfhack.HIDE_CONSOLE_ON_STARTUP``: Whether to hide the external DFHack + terminal window on startup. This, of course, is not useful to change + dynamically. You'll have to use `gui/control-panel` or edit the init file + directly and restart DF for it to have an effect. + +- ``dfhack.HIDE_ARMOK_TOOLS``: Whether to hide "armok" tools in command lists. + +- ``dfhack.SUPPRESS_DUPLICATE_KEYBOARD_EVENTS``: Whether to prevent DFHack + keybindings from producing DF key events. + Miscellaneous notes =================== This section is for odd but important notes that don't fit anywhere else. diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst index c9665a048..c6553e48c 100644 --- a/docs/builtins/keybinding.rst +++ b/docs/builtins/keybinding.rst @@ -33,6 +33,11 @@ The ```` parameter above has the following **case-sensitive** syntax:: where the ``KEY`` part can be any recognized key and :kbd:`[`:kbd:`]` denote optional parts. +DFHack commands can advertise the contexts in which they can be usefully run. +For example, a command that acts on a selected unit can tell `keybinding` that +it is not "applicable" in the current context if a unit is not actively +selected. + When multiple commands are bound to the same key combination, DFHack selects the first applicable one. Later ``add`` commands, and earlier entries within one ``add`` command have priority. Commands that are not specifically intended for diff --git a/docs/changelog.txt b/docs/changelog.txt index 21e2da45b..bb0512fab 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,20 +34,31 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins +- `3dveins`: reinstated for v50, this plugin replaces vanilla DF's blobby vein generation with veins that flow smoothly and naturally between z-levels ## Fixes +- Fix extra keys appearing in DFHack text boxes when shift (or any other modifier) is released before the other key you were pressing ## Misc Improvements - `autonick`: add more variety to nicknames based on famous literary dwarves +- ``widgets.EditField``: DFHack edit fields now support cut/copy/paste with the system clipboard with Ctrl-X/Ctrl-C/Ctrl-V +- Suppress DF keyboard events when a DFHack keybinding is matched. This prevents, for example, a backtick from appearing in a textbox as text when you launch `gui/launcher` from the backtick keybinding. ## Documentation +- `misery`: rewrite the documentation to clarify the actual effects of the plugin ## API +- ``Units::getUnitByNobleRole``, ``Units::getUnitsByNobleRole``: unit lookup API by role +- ``Items::markForTrade()``, ``Items::isRequestedTradeGood()``, ``Items::getValue``: see Lua notes below ## Internals +- Price calculations fixed for many item types ## Lua -- ``dfhack.items.markForTrade``: new API for marking items for trade +- ``dfhack.units.getUnitByNobleRole``, ``dfhack.units.getUnitsByNobleRole``: unit lookup API by role +- ``dfhack.items.markForTrade``: mark items for trade +- ``dfhack.items.isRequestedTradeGood``: discover whether an item is named in a trade agreement with an active caravan +- ``dfhack.items.getValue``: gained optional ``caravan`` and ``caravan_buying`` parameters for prices that take trader races and agreements into account ## Removed diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 33e27be2d..11bac3f61 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -1438,10 +1438,23 @@ Units module Note that ``pos2xyz()`` cannot currently be used to convert coordinate objects to the arguments required by this function. +* ``dfhack.units.getUnitByNobleRole(role_name)`` + + Returns the unit assigned to the given noble role, if any. ``role_name`` must + be one of the position codes associated with the active fort or civilization + government. For example: ``CAPTAIN_OF_THE_GUARD``, ``MAYOR``, or ``BARON``. + Note that if more than one unit has the role, only the first will be + returned. See ``getUnitsByNobleRole`` below for retrieving all units with a + particular role. + +* ``dfhack.units.getUnitsByNobleRole(role_name)`` + + Returns a list of units (possibly empty) assigned to the given noble role. + * ``dfhack.units.getCitizens([ignore_sanity])`` - Returns a table (list) of all citizens, which you would otherwise have to loop over all - units in world and test against ``isCitizen()`` to discover. + Returns a table (list) of all citizens, which you would otherwise have to + loop over all units in world and test against ``isCitizen()`` to discover. * ``dfhack.units.teleport(unit, pos)`` @@ -1755,9 +1768,20 @@ Items module Calculates the base value for an item of the specified type and material. -* ``dfhack.items.getValue(item)`` +* ``dfhack.items.getValue(item[, caravan_state, caravan_buying])`` + + Calculates the value of an item. If a ``df.caravan_state`` object is given + (from ``df.global.plotinfo.caravans`` or + ``df.global.main_interface.trade.mer``), then the value is modified by civ + properties and any trade agreements that might be in effect. In this case, + specify ``caravan_buying`` as ``true`` to get the price the caravan will pay + for the item and ``false`` to get the price that the caravan will sell the + item for. + +* ``dfhack.items.isRequestedTradeGood(item[, caravan_state])`` - Calculates the Basic Value of an item, as seen in the View Item screen. + Returns whether a caravan will pay extra for the given item. If caravan_state + is not given, checks all active caravans. * ``dfhack.items.createItem(item_type, item_subtype, mat_type, mat_index, unit)`` @@ -2819,6 +2843,13 @@ and are only documented here for completeness: Returns 0 if the address is not found. Requires a heap snapshot. +* ``dfhack.internal.getClipboardTextCp437()`` + + Gets the system clipboard text (and converts text to CP437 encoding). + +* ``dfhack.internal.setClipboardTextCp437(text)`` + + Sets the system clipboard text from a CP437 string. .. _lua-core-context: @@ -4692,6 +4723,12 @@ following keyboard hotkeys: - Ctrl-B/Ctrl-F: move the cursor one word back or forward. - Ctrl-A/Ctrl-E: move the cursor to the beginning/end of the text. +The widget also supports integration with the system clipboard: + +- Ctrl-C: copy current text to the system clipboard +- Ctrl-X: copy current text to the system clipboard and clear text in widget +- Ctrl-V: paste text from the system clipboard (text is converted to cp437) + The ``EditField`` class also provides the following functions: * ``editfield:setCursor([cursor_pos])`` diff --git a/docs/dev/compile/Compile.rst b/docs/dev/compile/Compile.rst index 22b9a7b1a..5e605e391 100644 --- a/docs/dev/compile/Compile.rst +++ b/docs/dev/compile/Compile.rst @@ -88,9 +88,9 @@ assistance. All Platforms ============= -Before you can compile the code you'll need to configure your build with cmake. Some IDEs can do this, -but from command line is the usual way to do this; thought the Windows section below points out some -Windows batch files that can be used to avoid opening a terminal/command-prompt. +Before you can compile the code you'll need to configure your build with cmake. Some IDEs can do this +for you, but it's more common to do it from the command line. Windows developers can refer to the +Windows section below for batch files that can be used to avoid opening a terminal/command-prompt. You should seek cmake's documentation online or via ``cmake --help`` to see how the command works. See the `build-options` page for help finding the DFHack build options relevant to you. diff --git a/docs/plugins/misery.rst b/docs/plugins/misery.rst index 8a65d8419..3bcd5588a 100644 --- a/docs/plugins/misery.rst +++ b/docs/plugins/misery.rst @@ -2,11 +2,12 @@ misery ====== .. dfhack-tool:: - :summary: Increase the intensity of your citizens' negative thoughts. + :summary: Make citizens more miserable. :tags: fort gameplay units -When enabled, negative thoughts that your citizens have will multiply by the -specified factor. This makes it more challenging to keep them happy. +When enabled, all of your citizens receive a negative thought about a +particularly nasty soapy bath. You can vary the strength of this negative +thought to increase or decrease the difficulty of keeping your citizens happy. Usage ----- @@ -18,18 +19,18 @@ Usage misery misery clear -The default misery factor is ``2``, meaning that your dwarves will become -miserable twice as fast. +The default misery factor is ``2``, which will result in a moderate hit to your +dwarves' happiness. Larger numbers increase the challenge. Examples -------- ``enable misery`` - Start multiplying bad thoughts for your citizens! + Start adding bad thoughts about nasty soapy baths to your citizens! ``misery 5`` - Make dwarves become unhappy 5 times faster than normal -- this is quite - challenging to handle! + Change the strength of the soapy bath negative thought to something quite + large -- this is very challenging to handle! ``misery clear`` Clear away negative thoughts added by ``misery``. Note that this will not diff --git a/library/Core.cpp b/library/Core.cpp index 4b036a83e..0a1c19351 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1404,7 +1404,7 @@ Core::~Core() } Core::Core() : - d(dts::make_unique()), + d(std::make_unique()), script_path_mutex{}, HotkeyMutex{}, HotkeyCond{}, @@ -1471,6 +1471,10 @@ std::string Core::getHackPath() #endif } +df::viewscreen * Core::getTopViewscreen() { + return getInstance().top_viewscreen; +} + bool Core::InitMainThread() { Filesystem::init(); @@ -1497,7 +1501,7 @@ bool Core::InitMainThread() { #else const char * path = "hack\\symbols.xml"; #endif - auto local_vif = dts::make_unique(); + auto local_vif = std::make_unique(); std::cerr << "Identifying DF version.\n"; try { @@ -1513,7 +1517,7 @@ bool Core::InitMainThread() { return false; } vif = std::move(local_vif); - auto local_p = dts::make_unique(*vif); + auto local_p = std::make_unique(*vif); local_p->ValidateDescriptionOS(); vinfo = local_p->getDescriptor(); @@ -1855,6 +1859,11 @@ void *Core::GetData( std::string key ) } } +Core& Core::getInstance() { + static Core instance; + return instance; +} + bool Core::isSuspended(void) { return ownerThread.load() == std::this_thread::get_id(); @@ -2347,11 +2356,21 @@ bool Core::DFH_ncurses_key(int key) return ncurses_wgetch(key, dummy); } +static bool getSuppressDuplicateKeyboardEvents() { + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + return DFHack::Lua::PushModulePublic(out, L, "dfhack", "SUPPRESS_DUPLICATE_KEYBOARD_EVENTS") && + lua_toboolean(L, -1); +} + // returns true if the event is handled bool Core::DFH_SDL_Event(SDL_Event* ev) { + static std::map hotkey_states; + // do NOT process events before we are ready. - if(!started || !ev) + if (!started || !ev) return false; if (ev->type == SDL_WINDOWEVENT && ev->window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { @@ -2363,23 +2382,40 @@ bool Core::DFH_SDL_Event(SDL_Event* ev) if (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) { auto &ke = ev->key; + auto &sym = ke.keysym.sym; - if (ke.keysym.sym == SDLK_LSHIFT || ke.keysym.sym == SDLK_RSHIFT) + if (sym == SDLK_LSHIFT || sym == SDLK_RSHIFT) modstate = (ev->type == SDL_KEYDOWN) ? modstate | DFH_MOD_SHIFT : modstate & ~DFH_MOD_SHIFT; - else if (ke.keysym.sym == SDLK_LCTRL || ke.keysym.sym == SDLK_RCTRL) + else if (sym == SDLK_LCTRL || sym == SDLK_RCTRL) modstate = (ev->type == SDL_KEYDOWN) ? modstate | DFH_MOD_CTRL : modstate & ~DFH_MOD_CTRL; - else if (ke.keysym.sym == SDLK_LALT || ke.keysym.sym == SDLK_RALT) + else if (sym == SDLK_LALT || sym == SDLK_RALT) modstate = (ev->type == SDL_KEYDOWN) ? modstate | DFH_MOD_ALT : modstate & ~DFH_MOD_ALT; - else if (ke.state == SDL_PRESSED && !hotkey_states[ke.keysym.sym]) + else if (ke.state == SDL_PRESSED && !hotkey_states[sym]) { - hotkey_states[ke.keysym.sym] = true; - SelectHotkey(ke.keysym.sym, modstate); + // the check against hotkey_states[sym] ensures we only process keybindings once per keypress + DEBUG(keybinding).print("key down: sym=%d (%c)\n", sym, sym); + bool handled = SelectHotkey(sym, modstate); + if (handled) { + DEBUG(keybinding).print("inhibiting SDL key down event\n"); + hotkey_states[sym] = true; + return getSuppressDuplicateKeyboardEvents(); + } } - else if(ke.state == SDL_RELEASED) + else if (ke.state == SDL_RELEASED) { - hotkey_states[ke.keysym.sym] = false; + DEBUG(keybinding).print("key up: sym=%d (%c)\n", sym, sym); + hotkey_states[sym] = false; } } + else if (ev->type == SDL_TEXTINPUT) { + auto &te = ev->text; + DEBUG(keybinding).print("text input: '%s'\n", te.text); + if (strlen(te.text) == 1 && hotkey_states[te.text[0]]) { + DEBUG(keybinding).print("inhibiting SDL text event\n"); + return true; + } + } + return false; } diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index f376edc6a..cd261d50f 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -213,12 +213,12 @@ std::string pointer_identity::getFullName() std::string container_identity::getFullName(type_identity *item) { - return "<" + (item ? item->getFullName() : std::string("void")) + ">"; + return '<' + (item ? item->getFullName() : std::string("void")) + '>'; } std::string ptr_container_identity::getFullName(type_identity *item) { - return "<" + (item ? item->getFullName() : std::string("void")) + "*>"; + return '<' + (item ? item->getFullName() : std::string("void")) + std::string("*>"); } std::string bit_container_identity::getFullName(type_identity *) diff --git a/library/Debug.cpp b/library/Debug.cpp index 9b13af168..dafbeb5ce 100644 --- a/library/Debug.cpp +++ b/library/Debug.cpp @@ -26,6 +26,7 @@ redistribute it freely, subject to the following restrictions: #include "Debug.h" #include "DebugManager.h" +#include #include #include #include diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 5d9411434..7afb3b9ea 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1833,6 +1833,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, multiplyGroupActionTimers), WRAPM(Units, setActionTimers), WRAPM(Units, setGroupActionTimers), + WRAPM(Units, getUnitByNobleRole), { NULL, NULL } }; @@ -1921,6 +1922,14 @@ static int units_getCitizens(lua_State *L) { return 0; } +static int units_getUnitsByNobleRole(lua_State *L) { + std::string role_name = lua_tostring(L, -1); + std::vector units; + Units::getUnitsByNobleRole(units, role_name); + Lua::PushVector(L, units); + return 1; +} + static int units_getStressCutoffs(lua_State *L) { lua_newtable(L); @@ -1935,6 +1944,7 @@ static const luaL_Reg dfhack_units_funcs[] = { { "getNoblePositions", units_getNoblePositions }, { "getUnitsInBox", units_getUnitsInBox }, { "getCitizens", units_getCitizens }, + { "getUnitsByNobleRole", units_getUnitsByNobleRole}, { "getStressCutoffs", units_getStressCutoffs }, { NULL, NULL } }; @@ -2010,6 +2020,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getSubtypeDef), WRAPM(Items, getItemBaseValue), WRAPM(Items, getValue), + WRAPM(Items, isRequestedTradeGood), WRAPM(Items, createItem), WRAPM(Items, checkMandates), WRAPM(Items, canTrade), @@ -2604,14 +2615,29 @@ static int screen_doSimulateInput(lua_State *L) int sz = lua_rawlen(L, 2); std::set keys; + char str = '\0'; for (int j = 1; j <= sz; j++) { lua_rawgeti(L, 2, j); - keys.insert((df::interface_key)lua_tointeger(L, -1)); + df::interface_key k = (df::interface_key)lua_tointeger(L, -1); + if (!str && k > df::interface_key::STRING_A000 && k <= df::interface_key::STRING_A255) + str = Screen::keyToChar(k); + keys.insert(k); lua_pop(L, 1); } + // if we're injecting a text keybinding, ensure it is reflected in the enabler text buffer + std::string prev_input; + if (str) { + prev_input = (const char *)&df::global::enabler->last_text_input[0]; + df::global::enabler->last_text_input[0] = str; + df::global::enabler->last_text_input[1] = '\0'; + } + screen->feed(&keys); + + if (str) + strcpy((char *)&df::global::enabler->last_text_input[0], prev_input.c_str()); return 0; } @@ -3014,6 +3040,8 @@ static const LuaWrapper::FunctionReg dfhack_internal_module[] = { WRAPN(getAddressSizeInHeap, get_address_size_in_heap), WRAPN(getRootAddressOfHeapObject, get_root_address_of_heap_object), WRAPN(msizeAddress, msize_address), + WRAP(getClipboardTextCp437), + WRAP(setClipboardTextCp437), { NULL, NULL } }; diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index b959e756e..7ffd4e1c6 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -27,6 +27,8 @@ distribution. #include "MiscUtils.h" #include "ColorText.h" +#include "modules/DFSDL.h" + #ifndef LINUX_BUILD // We don't want min and max macros #define NOMINMAX diff --git a/library/include/Core.h b/library/include/Core.h index 916a63cf3..180b5cc7b 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -40,8 +40,6 @@ distribution. #include #include -#include "RemoteClient.h" - #define DFH_MOD_SHIFT 1 #define DFH_MOD_CTRL 2 #define DFH_MOD_ALT 4 @@ -74,6 +72,17 @@ namespace DFHack struct Hide; } + enum command_result + { + CR_LINK_FAILURE = -3, // RPC call failed due to I/O or protocol error + CR_NEEDS_CONSOLE = -2, // Attempt to call interactive command without console + CR_NOT_IMPLEMENTED = -1, // Command not implemented, or plugin not loaded + CR_OK = 0, // Success + CR_FAILURE = 1, // Failure + CR_WRONG_USAGE = 2, // Wrong arguments or ui state + CR_NOT_FOUND = 3 // Target object not found (for RPC mainly) + }; + enum state_change_event { SC_UNKNOWN = -1, @@ -97,10 +106,14 @@ namespace DFHack StateChangeScript(state_change_event event, std::string path, bool save_specific = false) :event(event), path(path), save_specific(save_specific) { } - bool operator==(const StateChangeScript& other) + bool const operator==(const StateChangeScript& other) { return event == other.event && path == other.path && save_specific == other.save_specific; } + bool const operator!=(const StateChangeScript& other) + { + return !(operator==(other)); + } }; // Core is a singleton. Why? Because it is closely tied to SDL calls. It tracks the global state of DF. @@ -116,11 +129,7 @@ namespace DFHack friend bool ::dfhooks_ncurses_key(int key); public: /// Get the single Core instance or make one. - static Core& getInstance() - { - static Core instance; - return instance; - } + static Core& getInstance(); /// check if the activity lock is owned by this thread bool isSuspended(void); /// Is everything OK? @@ -168,7 +177,7 @@ namespace DFHack bool isWorldLoaded() { return (last_world_data_ptr != NULL); } bool isMapLoaded() { return (last_local_map_ptr != NULL && last_world_data_ptr != NULL); } - static df::viewscreen *getTopViewscreen() { return getInstance().top_viewscreen; } + static df::viewscreen *getTopViewscreen(); DFHack::Console &getConsole() { return con; } @@ -241,7 +250,6 @@ namespace DFHack int8_t modstate; std::map > key_bindings; - std::map hotkey_states; std::string hotkey_cmd; enum hotkey_set_t { NO, diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index d14bdb6e9..59adcdbe5 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -61,28 +61,6 @@ namespace DFHack { class color_ostream; } -/*! \namespace dts - * std.reverse() == dts, The namespace that include forward compatible helpers - * which can be used from newer standards. The preprocessor check prefers - * standard version if one is available. The standard version gets imported with - * using. - */ -namespace dts { -// Check if lib supports the feature test macro or version is over c++14. -#if __cpp_lib_make_unique < 201304 && __cplusplus < 201402L -//! Insert c++14 make_unique to be forward compatible. Array versions are -//! missing -template -typename std::enable_if::value, std::unique_ptr >::type -make_unique(Args&&... args) -{ - return std::unique_ptr{new T{std::forward(args)...}}; -} -#else /* >= c++14 */ -using std::make_unique; -#endif -} - template void print_bits ( T val, std::ostream& out ) { diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 78c5e5dc8..f67023f93 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -35,8 +35,6 @@ distribution. #include "Core.h" #include "DataFuncs.h" -#include "RemoteClient.h" - typedef struct lua_State lua_State; namespace tthread diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index e71b985cd..921d351c3 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -26,6 +26,7 @@ distribution. #include "Pragma.h" #include "Export.h" #include "ColorText.h" +#include "Core.h" class CPassiveSocket; class CActiveSocket; @@ -39,17 +40,6 @@ namespace DFHack using dfproto::IntMessage; using dfproto::StringMessage; - enum command_result - { - CR_LINK_FAILURE = -3, // RPC call failed due to I/O or protocol error - CR_NEEDS_CONSOLE = -2, // Attempt to call interactive command without console - CR_NOT_IMPLEMENTED = -1, // Command not implemented, or plugin not loaded - CR_OK = 0, // Success - CR_FAILURE = 1, // Failure - CR_WRONG_USAGE = 2, // Wrong arguments or ui state - CR_NOT_FOUND = 3 // Target object not found (for RPC mainly) - }; - enum DFHackReplyCode : int16_t { RPC_REPLY_RESULT = -1, RPC_REPLY_FAIL = -2, diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 78163108e..22dbb0370 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -237,7 +237,7 @@ DFHACK_EXPORT std::string getRoomDescription(df::building *building, df::unit *u * starting at the top left and moving right, row by row, * the block's items are checked for anything on the ground within that stockpile. */ -class DFHACK_EXPORT StockpileIterator : public std::iterator +class DFHACK_EXPORT StockpileIterator { df::building_stockpilest* stockpile; df::map_block* block; @@ -245,6 +245,12 @@ class DFHACK_EXPORT StockpileIterator : public std::iterator add_text_keys(const std::set& keys); + // returns a new set of interface keys that ensures that string input matches the DF text buffer + DFHACK_EXPORT std::set normalize_text_keys(const std::set& keys); /// Retrieve the string representation of the bound key. DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 4fd9246aa..3c56d0890 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -148,6 +148,8 @@ DFHACK_EXPORT df::unit *getUnit(const int32_t index); DFHACK_EXPORT bool getUnitsInBox(std::vector &units, int16_t x1, int16_t y1, int16_t z1, int16_t x2, int16_t y2, int16_t z2); +DFHACK_EXPORT bool getUnitsByNobleRole(std::vector &units, std::string noble); +DFHACK_EXPORT df::unit *getUnitByNobleRole(std::string noble); DFHACK_EXPORT bool getCitizens(std::vector &citizens, bool ignore_sanity = false); DFHACK_EXPORT int32_t findIndexById(int32_t id); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 8ea5e9dac..860b56cc1 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -63,6 +63,11 @@ function dfhack.getHideArmokTools() return dfhack.HIDE_ARMOK_TOOLS end +dfhack.SUPPRESS_DUPLICATE_KEYBOARD_EVENTS = true +function dfhack.getSuppressDuplicateKeyboardEvents() + return dfhack.SUPPRESS_DUPLICATE_KEYBOARD_EVENTS +end + -- Error handling safecall = dfhack.safecall diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 7a3201f28..b034ed1e8 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -640,8 +640,12 @@ function EditField:setCursor(cursor) end function EditField:setText(text, cursor) + local old = self.text self.text = text self:setCursor(cursor) + if self.on_change and text ~= old then + self.on_change(self.text, old) + end end function EditField:postUpdateLayout() @@ -699,11 +703,7 @@ function EditField:onInput(keys) end if self.key and (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) then - local old = self.text self:setText(self.saved_text) - if self.on_change and old ~= self.saved_text then - self.on_change(self.text, old) - end self:setFocus(false) return true end @@ -747,9 +747,6 @@ function EditField:onInput(keys) return self.modal end end - if self.on_change and self.text ~= old then - self.on_change(self.text, old) - end return true elseif keys.KEYBOARD_CURSOR_LEFT then self:setCursor(self.cursor - 1) @@ -759,9 +756,10 @@ function EditField:onInput(keys) find('.*[%w_%-][^%w_%-]') self:setCursor(prev_word_end or 1) return true - elseif keys.CUSTOM_CTRL_A then -- home - self:setCursor(1) - return true + -- commented out until we get HOME key support from DF + -- elseif keys.CUSTOM_CTRL_A then -- home + -- self:setCursor(1) + -- return true elseif keys.KEYBOARD_CURSOR_RIGHT then self:setCursor(self.cursor + 1) return true @@ -772,6 +770,16 @@ function EditField:onInput(keys) elseif keys.CUSTOM_CTRL_E then -- end self:setCursor() return true + elseif keys.CUSTOM_CTRL_C then + dfhack.internal.setClipboardTextCp437(self.text) + return true + elseif keys.CUSTOM_CTRL_X then + dfhack.internal.setClipboardTextCp437(self.text) + self:setText('') + return true + elseif keys.CUSTOM_CTRL_V then + self:insert(dfhack.internal.getClipboardTextCp437()) + return true end -- if we're modal, then unconditionally eat all the input diff --git a/library/modules/DFSDL.cpp b/library/modules/DFSDL.cpp index 7aa7f36d5..aa54cf66c 100644 --- a/library/modules/DFSDL.cpp +++ b/library/modules/DFSDL.cpp @@ -5,6 +5,8 @@ #include "Debug.h" #include "PluginManager.h" +#include + namespace DFHack { DBG_DECLARE(core, dfsdl, DebugCategory::LINFO); } @@ -35,6 +37,10 @@ void (*g_SDL_FreeSurface)(SDL_Surface *) = nullptr; // int (*g_SDL_SemWait)(DFSDL_sem *) = nullptr; // int (*g_SDL_SemPost)(DFSDL_sem *) = nullptr; int (*g_SDL_PushEvent)(SDL_Event *) = nullptr; +SDL_bool (*g_SDL_HasClipboardText)(); +int (*g_SDL_SetClipboardText)(const char *text); +char * (*g_SDL_GetClipboardText)(); +void (*g_SDL_free)(void *); bool DFSDL::init(color_ostream &out) { for (auto &lib_str : SDL_LIBS) { @@ -71,6 +77,10 @@ bool DFSDL::init(color_ostream &out) { // bind(g_sdl_handle, SDL_SemWait); // bind(g_sdl_handle, SDL_SemPost); bind(g_sdl_handle, SDL_PushEvent); + bind(g_sdl_handle, SDL_HasClipboardText); + bind(g_sdl_handle, SDL_SetClipboardText); + bind(g_sdl_handle, SDL_GetClipboardText); + bind(g_sdl_handle, SDL_free); #undef bind DEBUG(dfsdl,out).print("sdl successfully loaded\n"); @@ -124,3 +134,30 @@ void DFSDL::DFSDL_FreeSurface(SDL_Surface *surface) { int DFSDL::DFSDL_PushEvent(SDL_Event *event) { return g_SDL_PushEvent(event); } + +void DFSDL::DFSDL_free(void *ptr) { + g_SDL_free(ptr); +} + +char * DFSDL::DFSDL_GetClipboardText() { + return g_SDL_GetClipboardText(); +} + +int DFSDL::DFSDL_SetClipboardText(const char *text) { + return g_SDL_SetClipboardText(text); +} + +DFHACK_EXPORT std::string DFHack::getClipboardTextCp437() { + if (!g_sdl_handle || g_SDL_HasClipboardText() != SDL_TRUE) + return ""; + char *text = g_SDL_GetClipboardText(); + std::string textcp437 = UTF2DF(text); + DFHack::DFSDL::DFSDL_free(text); + return textcp437; +} + +DFHACK_EXPORT bool DFHack::setClipboardTextCp437(std::string text) { + if (!g_sdl_handle) + return false; + return 0 == DFHack::DFSDL::DFSDL_SetClipboardText(DF2UTF(text).c_str()); +} diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp index 1fd064d56..9aab53466 100644 --- a/library/modules/DFSteam.cpp +++ b/library/modules/DFSteam.cpp @@ -108,7 +108,7 @@ static bool is_running_on_wine() { return !!pwine_get_version; } -static DWORD findProcess(LPWSTR name) { +static DWORD findProcess(LPCWSTR name) { PROCESSENTRY32W entry; entry.dwSize = sizeof(PROCESSENTRY32W); @@ -150,11 +150,14 @@ static bool launchDFHack(color_ostream& out) { si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); - // note that the enviornment must be explicitly zeroed out and not NULL, + static LPCWSTR procname = L"hack/launchdf.exe"; + static const char * env = "\0"; + + // note that the environment must be explicitly zeroed out and not NULL, // otherwise the launched process will inherit this process's environment, // and the Steam API in the launchdf process will think it is in DF's context. - BOOL res = CreateProcessW(L"hack/launchdf.exe", - NULL, NULL, NULL, FALSE, 0, "\0", NULL, &si, &pi); + BOOL res = CreateProcessW(procname, + NULL, NULL, NULL, FALSE, 0, (LPVOID)env, NULL, &si, &pi); return !!res; } diff --git a/library/modules/Graphic.cpp b/library/modules/Graphic.cpp index 7f6b8f4d3..b55ee83ed 100644 --- a/library/modules/Graphic.cpp +++ b/library/modules/Graphic.cpp @@ -44,7 +44,7 @@ using namespace DFHack; std::unique_ptr DFHack::createGraphic() { - return dts::make_unique(); + return std::make_unique(); } bool Graphic::Register(DFTileSurface* (*func)(int,int)) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index dc6f47f59..102fef9d6 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -182,23 +182,23 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) if (game->main_interface.info.open) { newFocusString = baseFocus; newFocusString += "/Info"; - newFocusString += "/" + enum_item_key(game->main_interface.info.current_mode); + newFocusString += '/' + enum_item_key(game->main_interface.info.current_mode); switch(game->main_interface.info.current_mode) { case df::enums::info_interface_mode_type::CREATURES: - newFocusString += "/" + enum_item_key(game->main_interface.info.creatures.current_mode); + newFocusString += '/' + enum_item_key(game->main_interface.info.creatures.current_mode); break; case df::enums::info_interface_mode_type::BUILDINGS: - newFocusString += "/" + enum_item_key(game->main_interface.info.buildings.mode); + newFocusString += '/' + enum_item_key(game->main_interface.info.buildings.mode); break; case df::enums::info_interface_mode_type::LABOR: - newFocusString += "/" + enum_item_key(game->main_interface.info.labor.mode); + newFocusString += '/' + enum_item_key(game->main_interface.info.labor.mode); break; case df::enums::info_interface_mode_type::ARTIFACTS: - newFocusString += "/" + enum_item_key(game->main_interface.info.artifacts.mode); + newFocusString += '/' + enum_item_key(game->main_interface.info.artifacts.mode); break; case df::enums::info_interface_mode_type::JUSTICE: - newFocusString += "/" + enum_item_key(game->main_interface.info.justice.current_mode); + newFocusString += '/' + enum_item_key(game->main_interface.info.justice.current_mode); break; case df::enums::info_interface_mode_type::WORK_ORDERS: if (game->main_interface.info.work_orders.conditions.open) @@ -215,7 +215,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) if (game->main_interface.view_sheets.open) { newFocusString = baseFocus; newFocusString += "/ViewSheets"; - newFocusString += "/" + enum_item_key(game->main_interface.view_sheets.active_sheet); + newFocusString += '/' + enum_item_key(game->main_interface.view_sheets.active_sheet); if (game->main_interface.view_sheets.active_sheet == df::view_sheet_type::BUILDING) { auto bld = df::building::find(game->main_interface.view_sheets.viewing_bldid); if (bld) @@ -244,7 +244,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) newFocusString += "/Zone"; if (game->main_interface.civzone.cur_bld) { newFocusString += "/Some"; - newFocusString += "/" + enum_item_key(game->main_interface.civzone.cur_bld->type); + newFocusString += '/' + enum_item_key(game->main_interface.civzone.cur_bld->type); } break; case df::enums::main_bottom_mode_type::ZONE_PAINT: @@ -519,7 +519,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dungeonmode) if (!adventure) return; - focus += "/" + enum_item_key(adventure->menu); + focus += '/' + enum_item_key(adventure->menu); } */ @@ -1447,7 +1447,7 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce if (flags.bits.D_DISPLAY) { world->status.display_timer = ANNOUNCE_DISPLAY_TIME; - Gui::writeToGamelog("x" + to_string(repeat_count + 1)); + Gui::writeToGamelog('x' + to_string(repeat_count + 1)); } return -1; } @@ -1709,7 +1709,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) if (a_flags.bits.D_DISPLAY) { world->status.display_timer = r.display_timer; - Gui::writeToGamelog("x" + to_string(repeat_count + 1)); + Gui::writeToGamelog('x' + to_string(repeat_count + 1)); } DEBUG(gui).print("Announcement succeeded as repeat:\n%s\n", message.c_str()); return true; diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index f98c7c46b..3fc08813c 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -50,8 +50,16 @@ using namespace std; #include "df/building.h" #include "df/building_actual.h" #include "df/building_tradedepotst.h" +#include "df/builtin_mats.h" +#include "df/caravan_state.h" #include "df/caste_raw.h" #include "df/creature_raw.h" +#include "df/dfhack_material_category.h" +#include "df/entity_buy_prices.h" +#include "df/entity_buy_requests.h" +#include "df/entity_sell_category.h" +#include "df/entity_sell_prices.h" +#include "df/entity_raw.h" #include "df/general_ref.h" #include "df/general_ref_building_holderst.h" #include "df/general_ref_contained_in_itemst.h" @@ -828,15 +836,15 @@ std::string Items::getDescription(df::item *item, int type, bool decorate) item->getItemDescription(&tmp, type); if (decorate) { - if (item->flags.bits.foreign) - tmp = "(" + tmp + ")"; - addQuality(tmp, item->getQuality()); if (item->isImproved()) { tmp = '\xAE' + tmp + '\xAF'; // («) + tmp + (») addQuality(tmp, item->getImprovementQuality()); } + + if (item->flags.bits.foreign) + tmp = "(" + tmp + ")"; } return tmp; @@ -1197,18 +1205,24 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat switch (item_type) { case item_type::BAR: - case item_type::SMALLGEM: case item_type::BLOCKS: case item_type::SKIN_TANNED: value = 5; break; - case item_type::ROUGH: + case item_type::SMALLGEM: + value = 20; + break; + case item_type::BOULDER: case item_type::WOOD: value = 3; break; + case item_type::ROUGH: + value = 6; + break; + case item_type::DOOR: case item_type::FLOODGATE: case item_type::BED: @@ -1224,6 +1238,7 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::TABLE: case item_type::COFFIN: case item_type::BOX: + case item_type::BAG: case item_type::BIN: case item_type::ARMORSTAND: case item_type::WEAPONRACK: @@ -1367,6 +1382,7 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat case item_type::COIN: case item_type::GLOB: case item_type::ORTHOPEDIC_CAST: + case item_type::BRANCH: value = 1; break; @@ -1435,7 +1451,498 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat return value; } -int Items::getValue(df::item *item) +static int32_t get_war_multiplier(df::item *item, df::caravan_state *caravan) { + static const int32_t DEFAULT_WAR_MULTIPLIER = 256; + + if (!caravan) + return DEFAULT_WAR_MULTIPLIER; + auto caravan_he = df::historical_entity::find(caravan->entity); + if (!caravan_he) + return DEFAULT_WAR_MULTIPLIER; + int32_t war_alignment = caravan_he->entity_raw->sphere_alignment[df::sphere_type::WAR]; + if (war_alignment == DEFAULT_WAR_MULTIPLIER) + return DEFAULT_WAR_MULTIPLIER; + switch (item->getType()) { + case df::item_type::WEAPON: + { + auto weap_def = df::itemdef_weaponst::find(item->getSubtype()); + auto caravan_cre_raw = df::creature_raw::find(caravan_he->race); + if (!weap_def || !caravan_cre_raw || caravan_cre_raw->adultsize < weap_def->minimum_size) + return DEFAULT_WAR_MULTIPLIER; + break; + } + case df::item_type::ARMOR: + case df::item_type::SHOES: + case df::item_type::HELM: + case df::item_type::GLOVES: + case df::item_type::PANTS: + { + if (item->getEffectiveArmorLevel() <= 0) + return DEFAULT_WAR_MULTIPLIER; + auto caravan_cre_raw = df::creature_raw::find(caravan_he->race); + auto maker_cre_raw = df::creature_raw::find(item->getMakerRace()); + if (!caravan_cre_raw || !maker_cre_raw) + return DEFAULT_WAR_MULTIPLIER; + if (caravan_cre_raw->adultsize < ((maker_cre_raw->adultsize * 6) / 7)) + return DEFAULT_WAR_MULTIPLIER; + if (caravan_cre_raw->adultsize > ((maker_cre_raw->adultsize * 8) / 7)) + return DEFAULT_WAR_MULTIPLIER; + break; + } + case df::item_type::SHIELD: + case df::item_type::AMMO: + case df::item_type::BACKPACK: + case df::item_type::QUIVER: + break; + default: + return DEFAULT_WAR_MULTIPLIER; + } + return war_alignment; +} + +static const int32_t DEFAULT_AGREEMENT_MULTIPLIER = 128; + +static int32_t get_buy_request_multiplier(df::item *item, const df::entity_buy_prices *buy_prices) { + if (!buy_prices) + return DEFAULT_AGREEMENT_MULTIPLIER; + + int16_t item_type = item->getType(); + int16_t item_subtype = item->getSubtype(); + int16_t mat_type = item->getMaterial(); + int32_t mat_subtype = item->getMaterialIndex(); + + for (size_t idx = 0; idx < buy_prices->price.size(); ++idx) { + if (buy_prices->items->item_type[idx] != item_type) + continue; + if (buy_prices->items->item_subtype[idx] != -1 && buy_prices->items->item_subtype[idx] != item_subtype) + continue; + if (buy_prices->items->mat_types[idx] != -1 && buy_prices->items->mat_types[idx] != mat_type) + continue; + if (buy_prices->items->mat_indices[idx] != -1 && buy_prices->items->mat_indices[idx] != mat_subtype) + continue; + return buy_prices->price[idx]; + } + return DEFAULT_AGREEMENT_MULTIPLIER; +} + +template +static int get_price(const std::vector &res, int32_t val, const std::vector &pri) { + for (size_t idx = 0; idx < res.size(); ++idx) { + if (res[idx] == val && pri.size() > idx) + return pri[idx]; + } + return -1; +} + +template +static int get_price(const std::vector &mat_res, int32_t mat, const std::vector &gloss_res, int32_t gloss, const std::vector &pri) { + for (size_t idx = 0; idx < mat_res.size(); ++idx) { + if (mat_res[idx] == mat && (gloss_res[idx] == -1 || gloss_res[idx] == gloss) && pri.size() > idx) + return pri[idx]; + } + return -1; +} + +static const uint16_t PLANT_BASE = 419; +static const uint16_t NUM_PLANT_TYPES = 200; + +static int32_t get_sell_request_multiplier(df::item *item, const df::historical_entity::T_resources &resources, const std::vector *prices) { + static const df::dfhack_material_category silk_cat(df::dfhack_material_category::mask_silk); + static const df::dfhack_material_category yarn_cat(df::dfhack_material_category::mask_yarn); + static const df::dfhack_material_category leather_cat(df::dfhack_material_category::mask_leather); + + int16_t item_type = item->getType(); + int16_t item_subtype = item->getSubtype(); + int16_t mat_type = item->getMaterial(); + int32_t mat_subtype = item->getMaterialIndex(); + + bool inorganic = mat_type == df::builtin_mats::INORGANIC; + bool is_plant = (uint16_t)(mat_type - PLANT_BASE) < NUM_PLANT_TYPES; + + switch (item_type) { + case df::item_type::BAR: + if (inorganic) { + if (int32_t price = get_price(resources.metals, mat_subtype, prices[df::entity_sell_category::MetalBars]); price != -1) + return price; + } + break; + case df::item_type::SMALLGEM: + if (inorganic) { + if (int32_t price = get_price(resources.gems, mat_subtype, prices[df::entity_sell_category::SmallCutGems]); price != -1) + return price; + } + break; + case df::item_type::BLOCKS: + if (inorganic) { + if (int32_t price = get_price(resources.stones, mat_subtype, prices[df::entity_sell_category::StoneBlocks]); price != -1) + return price; + } + break; + case df::item_type::ROUGH: + if (int32_t price = get_price(resources.misc_mat.glass.mat_type, mat_type, resources.misc_mat.glass.mat_index, mat_subtype, + prices[df::entity_sell_category::Glass]); price != -1) + return price; + break; + case df::item_type::BOULDER: + if (int32_t price = get_price(resources.stones, mat_subtype, prices[df::entity_sell_category::Stone]); price != -1) + return price; + if (int32_t price = get_price(resources.misc_mat.clay.mat_type, mat_type, resources.misc_mat.clay.mat_index, mat_subtype, + prices[df::entity_sell_category::Clay]); price != -1) + return price; + break; + case df::item_type::WOOD: + if (int32_t price = get_price(resources.organic.wood.mat_type, mat_type, resources.organic.wood.mat_index, mat_subtype, + prices[df::entity_sell_category::Wood]); price != -1) + return price; + break; + case df::item_type::CHAIN: + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::RopesPlant]); price != -1) + return price; + } + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid()) { + if (mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::RopesSilk]); price != -1) + return price; + } + if (mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::RopesYarn]); price != -1) + return price; + } + } + } + break; + case df::item_type::FLASK: + if (int32_t price = get_price(resources.misc_mat.flasks.mat_type, mat_type, resources.misc_mat.flasks.mat_index, mat_subtype, + prices[df::entity_sell_category::FlasksWaterskins]); price != -1) + return price; + break; + case df::item_type::GOBLET: + if (int32_t price = get_price(resources.misc_mat.crafts.mat_type, mat_type, resources.misc_mat.crafts.mat_index, mat_subtype, + prices[df::entity_sell_category::CupsMugsGoblets]); price != -1) + return price; + break; + case df::item_type::INSTRUMENT: + if (int32_t price = get_price(resources.instrument_type, mat_subtype, prices[df::entity_sell_category::Instruments]); price != -1) + return price; + break; + case df::item_type::TOY: + if (int32_t price = get_price(resources.toy_type, mat_subtype, prices[df::entity_sell_category::Toys]); price != -1) + return price; + break; + case df::item_type::CAGE: + if (int32_t price = get_price(resources.misc_mat.cages.mat_type, mat_type, resources.misc_mat.cages.mat_index, mat_subtype, + prices[df::entity_sell_category::Cages]); price != -1) + return price; + break; + case df::item_type::BARREL: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Barrels]); price != -1) + return price; + break; + case df::item_type::BUCKET: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Buckets]); price != -1) + return price; + break; + case df::item_type::WEAPON: + if (int32_t price = get_price(resources.weapon_type, mat_subtype, prices[df::entity_sell_category::Weapons]); price != -1) + return price; + if (int32_t price = get_price(resources.digger_type, mat_subtype, prices[df::entity_sell_category::DiggingImplements]); price != -1) + return price; + if (int32_t price = get_price(resources.training_weapon_type, mat_subtype, prices[df::entity_sell_category::TrainingWeapons]); price != -1) + return price; + break; + case df::item_type::ARMOR: + if (int32_t price = get_price(resources.armor_type, mat_subtype, prices[df::entity_sell_category::Bodywear]); price != -1) + return price; + break; + case df::item_type::SHOES: + if (int32_t price = get_price(resources.shoes_type, mat_subtype, prices[df::entity_sell_category::Footwear]); price != -1) + return price; + break; + case df::item_type::SHIELD: + if (int32_t price = get_price(resources.shield_type, mat_subtype, prices[df::entity_sell_category::Shields]); price != -1) + return price; + break; + case df::item_type::HELM: + if (int32_t price = get_price(resources.helm_type, mat_subtype, prices[df::entity_sell_category::Headwear]); price != -1) + return price; + break; + case df::item_type::GLOVES: + if (int32_t price = get_price(resources.gloves_type, mat_subtype, prices[df::entity_sell_category::Handwear]); price != -1) + return price; + break; + case df::item_type::BAG: + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid() && mi.matches(leather_cat)) { + if (int32_t price = get_price(resources.organic.leather.mat_type, mat_type, resources.organic.leather.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsLeather]); price != -1) + return price; + } + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsPlant]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsSilk]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::BagsYarn]); price != -1) + return price; + } + } + break; + case df::item_type::FIGURINE: + case df::item_type::AMULET: + case df::item_type::SCEPTER: + case df::item_type::CROWN: + case df::item_type::RING: + case df::item_type::EARRING: + case df::item_type::BRACELET: + case df::item_type::TOTEM: + case df::item_type::BOOK: + if (int32_t price = get_price(resources.misc_mat.crafts.mat_type, mat_type, resources.misc_mat.crafts.mat_index, mat_subtype, + prices[df::entity_sell_category::Crafts]); price != -1) + return price; + break; + case df::item_type::AMMO: + if (int32_t price = get_price(resources.ammo_type, mat_subtype, prices[df::entity_sell_category::Ammo]); price != -1) + return price; + break; + case df::item_type::GEM: + if (inorganic) { + if (int32_t price = get_price(resources.gems, mat_subtype, prices[df::entity_sell_category::LargeCutGems]); price != -1) + return price; + } + break; + case df::item_type::ANVIL: + if (int32_t price = get_price(resources.metal.anvil.mat_type, mat_type, resources.metal.anvil.mat_index, mat_subtype, + prices[df::entity_sell_category::Anvils]); price != -1) + return price; + break; + case df::item_type::MEAT: + if (int32_t price = get_price(resources.misc_mat.meat.mat_type, mat_type, resources.misc_mat.meat.mat_index, mat_subtype, + prices[df::entity_sell_category::Meat]); price != -1) + return price; + break; + case df::item_type::FISH: + case df::item_type::FISH_RAW: + if (int32_t price = get_price(resources.fish_races, mat_type, resources.fish_castes, mat_subtype, + prices[df::entity_sell_category::Fish]); price != -1) + return price; + break; + case df::item_type::VERMIN: + case df::item_type::PET: + if (int32_t price = get_price(resources.animals.pet_races, mat_type, resources.animals.pet_castes, mat_subtype, + prices[df::entity_sell_category::Pets]); price != -1) + return price; + break; + case df::item_type::SEEDS: + if (int32_t price = get_price(resources.seeds.mat_type, mat_type, resources.seeds.mat_index, mat_subtype, + prices[df::entity_sell_category::Seeds]); price != -1) + return price; + break; + case df::item_type::PLANT: + if (int32_t price = get_price(resources.plants.mat_type, mat_type, resources.plants.mat_index, mat_subtype, + prices[df::entity_sell_category::Plants]); price != -1) + return price; + break; + case df::item_type::SKIN_TANNED: + if (int32_t price = get_price(resources.organic.leather.mat_type, mat_type, resources.organic.leather.mat_index, mat_subtype, + prices[df::entity_sell_category::Leather]); price != -1) + return price; + break; + case df::item_type::PLANT_GROWTH: + if (is_plant) { + if (int32_t price = get_price(resources.tree_fruit_plants, mat_type, resources.tree_fruit_growths, mat_subtype, + prices[df::entity_sell_category::FruitsNuts]); price != -1) + return price; + if (int32_t price = get_price(resources.shrub_fruit_plants, mat_type, resources.shrub_fruit_growths, mat_subtype, + prices[df::entity_sell_category::GardenVegetables]); price != -1) + return price; + } + break; + case df::item_type::THREAD: + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::ThreadPlant]); price != -1) + return price; + } + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid() && mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::ThreadSilk]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::ThreadYarn]); price != -1) + return price; + } + } + break; + case df::item_type::CLOTH: + if (is_plant) { + if (int32_t price = get_price(resources.organic.fiber.mat_type, mat_type, resources.organic.fiber.mat_index, mat_subtype, + prices[df::entity_sell_category::ClothPlant]); price != -1) + return price; + } + { + MaterialInfo mi; + mi.decode(mat_type, mat_subtype); + if (mi.isValid() && mi.matches(silk_cat)) { + if (int32_t price = get_price(resources.organic.silk.mat_type, mat_type, resources.organic.silk.mat_index, mat_subtype, + prices[df::entity_sell_category::ClothSilk]); price != -1) + return price; + } + if (mi.isValid() && mi.matches(yarn_cat)) { + if (int32_t price = get_price(resources.organic.wool.mat_type, mat_type, resources.organic.wool.mat_index, mat_subtype, + prices[df::entity_sell_category::ClothYarn]); price != -1) + return price; + } + } + break; + case df::item_type::PANTS: + if (int32_t price = get_price(resources.pants_type, mat_subtype, prices[df::entity_sell_category::Legwear]); price != -1) + return price; + break; + case df::item_type::BACKPACK: + if (int32_t price = get_price(resources.misc_mat.backpacks.mat_type, mat_type, resources.misc_mat.backpacks.mat_index, mat_subtype, + prices[df::entity_sell_category::Backpacks]); price != -1) + return price; + break; + case df::item_type::QUIVER: + if (int32_t price = get_price(resources.misc_mat.quivers.mat_type, mat_type, resources.misc_mat.quivers.mat_index, mat_subtype, + prices[df::entity_sell_category::Quivers]); price != -1) + return price; + break; + case df::item_type::TRAPCOMP: + if (int32_t price = get_price(resources.trapcomp_type, mat_subtype, prices[df::entity_sell_category::TrapComponents]); price != -1) + return price; + break; + case df::item_type::DRINK: + if (int32_t price = get_price(resources.misc_mat.booze.mat_type, mat_type, resources.misc_mat.booze.mat_index, mat_subtype, + prices[df::entity_sell_category::Drinks]); price != -1) + return price; + break; + case df::item_type::POWDER_MISC: + if (int32_t price = get_price(resources.misc_mat.powders.mat_type, mat_type, resources.misc_mat.powders.mat_index, mat_subtype, + prices[df::entity_sell_category::Powders]); price != -1) + return price; + if (int32_t price = get_price(resources.misc_mat.sand.mat_type, mat_type, resources.misc_mat.sand.mat_index, mat_subtype, + prices[df::entity_sell_category::Sand]); price != -1) + return price; + break; + case df::item_type::CHEESE: + if (int32_t price = get_price(resources.misc_mat.cheese.mat_type, mat_type, resources.misc_mat.cheese.mat_index, mat_subtype, + prices[df::entity_sell_category::Cheese]); price != -1) + return price; + break; + case df::item_type::LIQUID_MISC: + if (int32_t price = get_price(resources.misc_mat.extracts.mat_type, mat_type, resources.misc_mat.extracts.mat_index, mat_subtype, + prices[df::entity_sell_category::Extracts]); price != -1) + return price; + break; + case df::item_type::SPLINT: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Splints]); price != -1) + return price; + break; + case df::item_type::CRUTCH: + if (int32_t price = get_price(resources.misc_mat.barrels.mat_type, mat_type, resources.misc_mat.barrels.mat_index, mat_subtype, + prices[df::entity_sell_category::Crutches]); price != -1) + return price; + break; + case df::item_type::TOOL: + if (int32_t price = get_price(resources.tool_type, mat_subtype, prices[df::entity_sell_category::Tools]); price != -1) + return price; + break; + case df::item_type::EGG: + if (int32_t price = get_price(resources.egg_races, mat_type, resources.egg_castes, mat_subtype, + prices[df::entity_sell_category::Eggs]); price != -1) + return price; + break; + case df::item_type::SHEET: + if (int32_t price = get_price(resources.organic.parchment.mat_type, mat_type, resources.organic.parchment.mat_index, mat_subtype, + prices[df::entity_sell_category::Parchment]); price != -1) + return price; + break; + default: + break; + } + + for (size_t idx = 0; idx < resources.wood_products.item_type.size(); ++idx) { + if (resources.wood_products.item_type[idx] == item_type && + (resources.wood_products.item_subtype[idx] == -1 || resources.wood_products.item_subtype[idx] == item_subtype) && + resources.wood_products.material.mat_type[idx] == mat_type && + (resources.wood_products.material.mat_index[idx] == -1 || resources.wood_products.material.mat_index[idx] == mat_subtype) && + prices[df::entity_sell_category::Miscellaneous].size() > idx) + return prices[df::entity_sell_category::Miscellaneous][idx]; + } + + return DEFAULT_AGREEMENT_MULTIPLIER; +} + +static int32_t get_sell_request_multiplier(df::item *item, const df::caravan_state *caravan) { + const df::entity_sell_prices *sell_prices = caravan->sell_prices; + if (!sell_prices) + return DEFAULT_AGREEMENT_MULTIPLIER; + + auto caravan_he = df::historical_entity::find(caravan->entity); + if (!caravan_he) + return DEFAULT_AGREEMENT_MULTIPLIER; + + return get_sell_request_multiplier(item, caravan_he->resources, &sell_prices->price[0]); +} + +static int32_t get_trade_agreement_multiplier(df::item *item, const df::caravan_state *caravan, bool caravan_buying) { + if (!caravan) + return DEFAULT_AGREEMENT_MULTIPLIER; + return caravan_buying ? get_buy_request_multiplier(item, caravan->buy_prices) + : get_sell_request_multiplier(item, caravan); +} + +static bool is_requested_trade_good(df::item *item, df::caravan_state *caravan) { + auto trade_state = caravan->trade_state; + if (caravan->time_remaining <= 0 || + (trade_state != df::caravan_state::T_trade_state::Approaching && + trade_state != df::caravan_state::T_trade_state::AtDepot)) + return false; + return get_buy_request_multiplier(item, caravan->buy_prices) > DEFAULT_AGREEMENT_MULTIPLIER; +} + +bool Items::isRequestedTradeGood(df::item *item, df::caravan_state *caravan) { + if (caravan) + return is_requested_trade_good(item, caravan); + + for (auto caravan : df::global::plotinfo->caravans) { + auto trade_state = caravan->trade_state; + if (caravan->time_remaining <= 0 || + (trade_state != df::caravan_state::T_trade_state::Approaching && + trade_state != df::caravan_state::T_trade_state::AtDepot)) + continue; + if (get_buy_request_multiplier(item, caravan->buy_prices) > DEFAULT_AGREEMENT_MULTIPLIER) + return true; + } + return false; +} + +int Items::getValue(df::item *item, df::caravan_state *caravan, bool caravan_buying) { CHECK_NULL_POINTER(item); @@ -1447,16 +1954,38 @@ int Items::getValue(df::item *item) // Get base value for item type, subtype, and material int value = getItemBaseValue(item_type, item_subtype, mat_type, mat_subtype); - // Ignore entity value modifications + // entity value modifications + value *= get_war_multiplier(item, caravan); + value >>= 8; // Improve value based on quality - int quality = item->getQuality(); - value *= (quality + 1); - if (quality == 5) + switch (item->getQuality()) { + case 1: + value *= 1.1; + value += 3; + break; + case 2: + value *= 1.2; + value += 6; + break; + case 3: + value *= 1.333; + value += 10; + break; + case 4: + value *= 1.5; + value += 15; + break; + case 5: value *= 2; + value += 30; + break; + default: + break; + } // Add improvement values - int impValue = item->getThreadDyeValue(NULL) + item->getImprovementsValue(NULL); + int impValue = item->getThreadDyeValue(caravan) + item->getImprovementsValue(caravan); if (item_type == item_type::AMMO) // Ammo improvements are worth less impValue /= 30; value += impValue; @@ -1481,12 +2010,16 @@ int Items::getValue(df::item *item) if (item->flags.bits.artifact_mood) value *= 10; + // modify buy/sell prices + value *= get_trade_agreement_multiplier(item, caravan, caravan_buying); + value >>= 7; + // Boost value from stack size value *= item->getStackSize(); // ...but not for coins if (item_type == item_type::COIN) { - value /= 500; + value /= 50; if (!value) value = 1; } diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 7a1ef249f..7922b5417 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -604,7 +604,7 @@ bool DFHack::isStoneInorganic(int material) std::unique_ptr DFHack::createMaterials() { - return dts::make_unique(); + return std::make_unique(); } Materials::Materials() diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 76f782edb..c7ef88c60 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -585,8 +585,10 @@ void Hide::merge() { } } } -std::set Screen::add_text_keys(const std::set& keys) { - std::set combined_keys(keys); +std::set Screen::normalize_text_keys(const std::set& keys) { + std::set combined_keys; + std::copy_if(keys.begin(), keys.end(), std::inserter(combined_keys, combined_keys.begin()), + [](df::interface_key k){ return k <= df::interface_key::STRING_A000 || k > df::interface_key::STRING_A255; } ); if (df::global::enabler->last_text_input[0]) { char c = df::global::enabler->last_text_input[0]; df::interface_key key = charToKey(c); @@ -952,7 +954,7 @@ int dfhack_lua_viewscreen::do_input(lua_State *L) } lua_pushvalue(L, -2); - Lua::PushInterfaceKeys(L, Screen::add_text_keys(*keys)); + Lua::PushInterfaceKeys(L, Screen::normalize_text_keys(*keys)); lua_call(L, 2, 0); self->update_focus(L, -1); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 533b40ca8..737ff0982 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -59,6 +59,7 @@ using namespace std; #include "df/entity_position_assignment.h" #include "df/entity_raw.h" #include "df/entity_raw_flags.h" +#include "df/entity_site_link.h" #include "df/identity_type.h" #include "df/game_mode.h" #include "df/histfig_entity_link_positionst.h" @@ -80,6 +81,8 @@ using namespace std; #include "df/unit_soul.h" #include "df/unit_wound.h" #include "df/world.h" +#include "df/world_data.h" +#include "df/world_site.h" #include "df/unit_action.h" #include "df/unit_action_type_group.h" @@ -770,6 +773,60 @@ bool Units::getUnitsInBox (std::vector &units, return true; } +static int32_t get_noble_position_id(const df::historical_entity::T_positions &positions, const string &noble) { + string target_id = toUpper(noble); + for (auto &position : positions.own) { + if (position->code == target_id) + return position->id; + } + return -1; +} + +static void add_assigned_noble_units(vector &units, const df::historical_entity::T_positions &positions, int32_t noble_position_id, size_t limit) { + for (auto &assignment : positions.assignments) { + if (assignment->position_id != noble_position_id) + continue; + auto histfig = df::historical_figure::find(assignment->histfig); + if (!histfig) + continue; + auto unit = df::unit::find(histfig->unit_id); + if (!unit) + continue; + units.emplace_back(unit); + if (limit > 0 && units.size() >= limit) + break; + } +} + +static void get_units_by_noble_role(vector &units, string noble, size_t limit = 0) { + auto &site = df::global::world->world_data->active_site[0]; + for (auto &link : site->entity_links) { + auto he = df::historical_entity::find(link->entity_id); + if (!he || + (he->type != df::historical_entity_type::SiteGovernment && + he->type != df::historical_entity_type::Civilization)) + continue; + int32_t noble_position_id = get_noble_position_id(he->positions, noble); + if (noble_position_id < 0) + continue; + add_assigned_noble_units(units, he->positions, noble_position_id, limit); + } +} + +bool Units::getUnitsByNobleRole(vector &units, std::string noble) { + units.clear(); + get_units_by_noble_role(units, noble); + return !units.empty(); +} + +df::unit *Units::getUnitByNobleRole(string noble) { + vector units; + get_units_by_noble_role(units, noble, 1); + if (units.empty()) + return NULL; + return units[0]; +} + bool Units::getCitizens(std::vector &citizens, bool ignore_sanity) { for (auto &unit : world->units.active) { if (isCitizen(unit, ignore_sanity)) diff --git a/library/xml b/library/xml index 5da8a785c..19dd3a941 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5da8a785cf176f831a56f934ec8ec6069a965ecf +Subproject commit 19dd3a94162a2ff3374cbdb1cadc92b55a626c92 diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index eaf741caf..6b95c2e87 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -7,15 +7,16 @@ #include "Core.h" #include "Console.h" +#include "DataDefs.h" +#include "Debug.h" #include "Export.h" +#include "MiscUtils.h" #include "PluginManager.h" + #include "modules/MapCache.h" #include "modules/Random.h" #include "modules/World.h" -#include "MiscUtils.h" - -#include "DataDefs.h" #include "df/world.h" #include "df/world_data.h" #include "df/world_region_details.h" @@ -47,6 +48,10 @@ DFHACK_PLUGIN("3dveins"); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(gametype); +namespace DFHack { + DBG_DECLARE(_3dveins, process, DebugCategory::LINFO); +} + command_result cmd_3dveins(color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) @@ -431,11 +436,13 @@ struct GeoLayer void print_mineral_stats(color_ostream &out) { for (auto it = mineral_count.begin(); it != mineral_count.end(); ++it) - out << " " << MaterialInfo(0,it->first.first).getToken() - << " " << ENUM_KEY_STR(inclusion_type,it->first.second) - << ": \t\t" << it->second << " (" << (float(it->second)/unmined_tiles) << ")" << std::endl; + INFO(process, out).print("3dveins: %s %s: %d (%f)\n", + MaterialInfo(0, it->first.first).getToken().c_str(), + ENUM_KEY_STR(inclusion_type, it->first.second).c_str(), + it->second, + (float(it->second) / unmined_tiles)); - out.print(" Total tiles: %d (%d unmined)\n", tiles, unmined_tiles); + INFO(process, out).print ("3dveins: Total tiles: %d (%d unmined)\n", tiles, unmined_tiles); } bool form_veins(color_ostream &out); @@ -467,12 +474,12 @@ struct GeoBiome void print_mineral_stats(color_ostream &out) { - out.print("Geological biome %d:\n", info.geo_index); + INFO(process,out).print("3dveins: Geological biome %d:\n", info.geo_index); for (size_t i = 0; i < layers.size(); i++) if (layers[i]) { - out << " Layer " << i << std::endl; + INFO(process, out).print("3dveins: Layer %ld\n", i); layers[i]->print_mineral_stats(out); } } @@ -586,7 +593,7 @@ bool VeinGenerator::init_biomes() if (info.geo_index < 0 || !info.geobiome) { - out.printerr("Biome %zd is not defined.\n", i); + WARN(process, out).print("Biome %zd is not defined.\n", i); return false; } @@ -797,8 +804,7 @@ bool VeinGenerator::scan_layer_depth(Block *b, df::coord2d column, int z) { if (z != min_level[idx]-1 && min_level[idx] <= top_solid) { - out.printerr( - "Discontinuous layer %d at (%d,%d,%d).\n", + WARN(process, out).print("Discontinuous layer %d at (%d,%d,%d).\n", layer->index, x+column.x*16, y+column.y*16, z ); return false; @@ -848,7 +854,7 @@ bool VeinGenerator::adjust_layer_depth(df::coord2d column) if (max_level[i+1] != min_level[i]-1) { - out.printerr( + WARN(process, out).print( "Gap or overlap with next layer %d at (%d,%d,%d-%d).\n", i+1, x+column.x*16, y+column.y*16, max_level[i+1], min_level[i] ); @@ -891,7 +897,7 @@ bool VeinGenerator::adjust_layer_depth(df::coord2d column) } } - out.printerr( + WARN(process, out).print( "Layer height change in layer %d at (%d,%d,%d): %d instead of %d.\n", i, x+column.x*16, y+column.y*16, max_level[i], size, biome->layers[i]->thickness @@ -914,6 +920,7 @@ bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) for (int y = 0; y < 16; y++) { df::coord2d tile(x,y); + GeoLayer *layer = mapLayer(b, tile); if (!layer) continue; @@ -932,7 +939,7 @@ bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) if (unsigned(key.first) >= materials.size() || unsigned(key.second) >= NUM_INCLUSIONS) { - out.printerr("Invalid vein code: %d %d - aborting.\n",key.first,key.second); + WARN(process, out).print("Invalid vein code: %d %d - aborting.\n",key.first,key.second); return false; } @@ -941,7 +948,7 @@ bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) if (status == -1) { // Report first occurence of unreasonable vein spec - out.printerr( + WARN(process, out).print( "Unexpected vein %s %s - ", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str() @@ -949,9 +956,9 @@ bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) status = materials[key.first].default_type; if (status < 0) - out.printerr("will be left in place.\n"); + WARN(process, out).print("will be left in place.\n"); else - out.printerr( + WARN(process, out).print( "correcting to %s.\n", ENUM_KEY_STR(inclusion_type, df::inclusion_type(status)).c_str() ); @@ -1090,7 +1097,7 @@ void VeinGenerator::write_block_tiles(Block *b, df::coord2d column, int z) if (!ok) { - out.printerr( + WARN(process, out).print( "Couldn't write %d vein at (%d,%d,%d)\n", mat, x+column.x*16, y+column.y*16, z ); @@ -1281,7 +1288,7 @@ bool GeoLayer::form_veins(color_ostream &out) if (parent_id >= (int)refs.size()) { - out.printerr("Forward vein reference in biome %d.\n", biome->info.geo_index); + WARN(process, out).print("Forward vein reference in biome %d.\n", biome->info.geo_index); return false; } @@ -1301,7 +1308,7 @@ bool GeoLayer::form_veins(color_ostream &out) if (vptr->parent) ctx = "only be in "+MaterialInfo(0,vptr->parent_mat()).getToken(); - out.printerr( + WARN(process, out).print( "Duplicate vein %s %s in biome %d layer %d - will %s.\n", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str(), @@ -1357,13 +1364,13 @@ bool VeinGenerator::place_orphan(t_veinkey key, int size, GeoLayer *from) if (best.empty()) { - out.printerr( + WARN(process,out).print( "Could not place orphaned vein %s %s anywhere.\n", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str() ); - return false; + return true; } for (auto it = best.begin(); size > 0 && it != best.end(); ++it) @@ -1391,7 +1398,7 @@ bool VeinGenerator::place_orphan(t_veinkey key, int size, GeoLayer *from) if (size > 0) { - out.printerr( + WARN(process, out).print( "Could not place all of orphaned vein %s %s: %d left.\n", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str(), @@ -1541,7 +1548,7 @@ bool VeinGenerator::place_veins(bool verbose) if (!isStoneInorganic(key.first)) { - out.printerr( + WARN(process, out).print( "Invalid vein material: %s\n", MaterialInfo(0, key.first).getToken().c_str() ); @@ -1551,7 +1558,7 @@ bool VeinGenerator::place_veins(bool verbose) if (!is_valid_enum_item(key.second)) { - out.printerr("Invalid vein type: %d\n", key.second); + WARN(process, out).print("Invalid vein type: %d\n", key.second); return false; } @@ -1564,13 +1571,13 @@ bool VeinGenerator::place_veins(bool verbose) sort(queue.begin(), queue.end(), vein_cmp); // Place tiles - out.print("Processing... (%zu)", queue.size()); + TRACE(process,out).print("Processing... (%zu)", queue.size()); for (size_t j = 0; j < queue.size(); j++) { if (queue[j]->parent && !queue[j]->parent->placed) { - out.printerr( + WARN(process, out).print( "\nParent vein not placed for %s %s.\n", MaterialInfo(0,queue[j]->vein.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, queue[j]->vein.second).c_str() @@ -1582,9 +1589,11 @@ bool VeinGenerator::place_veins(bool verbose) if (verbose) { if (j > 0) - out.print("done."); + { + TRACE(process, out).print("done."); + } - out.print( + TRACE(process, out).print( "\nVein layer %zu of %zu: %s %s (%.2f%%)... ", j+1, queue.size(), MaterialInfo(0,queue[j]->vein.first).getToken().c_str(), @@ -1594,14 +1603,13 @@ bool VeinGenerator::place_veins(bool verbose) } else { - out.print("\rVein layer %zu of %zu... ", j+1, queue.size()); - out.flush(); + TRACE(process, out).print("\rVein layer %zu of %zu... ", j+1, queue.size()); } queue[j]->place_tiles(); } - out.print("done.\n"); + TRACE(process, out).print("done.\n"); return true; } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4029b8e2e..63e5274ae 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -73,7 +73,7 @@ set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) # If you are adding a plugin that you do not intend to commit to the DFHack repo, # see instructions for adding "external" plugins at the end of this file. -#dfhack_plugin(3dveins 3dveins.cpp) +dfhack_plugin(3dveins 3dveins.cpp) dfhack_plugin(add-spatter add-spatter.cpp) dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua) dfhack_plugin(autochop autochop.cpp LINK_LIBRARIES lua) diff --git a/plugins/autofarm.cpp b/plugins/autofarm.cpp index 44253b2f9..e1511153c 100644 --- a/plugins/autofarm.cpp +++ b/plugins/autofarm.cpp @@ -436,7 +436,7 @@ DFhackCExport command_result plugin_init(color_ostream& out, std::vector ()); + autofarmInstance = std::move(std::make_unique()); autofarmInstance->load_state(out); return CR_OK; } diff --git a/plugins/channel-safely/channel-manager.cpp b/plugins/channel-safely/channel-manager.cpp index aa7a24461..67f8742b3 100644 --- a/plugins/channel-safely/channel-manager.cpp +++ b/plugins/channel-safely/channel-manager.cpp @@ -103,7 +103,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool WARN(manager).print(" has %d access\n", access); cavein_possible = config.riskaverse; cavein_candidates.emplace(pos, access); - least_access = min(access, least_access); + least_access = std::min(access, least_access); } } else if (config.insta_dig && isEntombed(miner_pos, pos)) { manage_one(pos, true, false); @@ -141,7 +141,7 @@ void ChannelManager::manage_group(const Group &group, bool set_marker_mode, bool for (df::block_square_event* event: block->block_events) { if (auto evT = virtual_cast(event)) { // we want to let the user keep some designations free of being managed - auto b = max(0, cavein_candidates[pos] - least_access); + auto b = std::max(0, cavein_candidates[pos] - least_access); auto v = 1000 + (b * 1700); DEBUG(manager).print("(" COORD ") 1000+1000(%d) -> %d {least-access: %d}\n",COORDARGS(pos), b, v, least_access); evT->priority[Coord(local)] = v; diff --git a/plugins/channel-safely/include/inlines.h b/plugins/channel-safely/include/inlines.h index a29f5a04d..362fd927a 100644 --- a/plugins/channel-safely/include/inlines.h +++ b/plugins/channel-safely/include/inlines.h @@ -23,7 +23,7 @@ namespace CSP { inline uint32_t calc_distance(df::coord p1, df::coord p2) { // calculate chebyshev (chessboard) distance uint32_t distance = abs(p2.z - p1.z); - distance += max(abs(p2.x - p1.x), abs(p2.y - p1.y)); + distance += std::max(abs(p2.x - p1.x), abs(p2.y - p1.y)); return distance; } diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index 1dfb6809d..bd6e41b64 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -411,7 +411,7 @@ public: Screen::paintTile(corner_ur, x2, y1); Screen::paintTile(corner_dl, x1, y2); Screen::paintTile(corner_dr, x2, y2); - string title = " " + get_title() + " "; + string title = ' ' + get_title() + ' '; Screen::paintString(Screen::Pen(' ', COLOR_DARKGREY, COLOR_BLACK), x2 - 6, y1, "DFHack"); Screen::paintString(Screen::Pen(' ', COLOR_BLACK, COLOR_GREY), diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index 136ad7a9d..ef2ca9422 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -103,7 +103,7 @@ static void find_active_keybindings(color_ostream &out, df::viewscreen *screen, } for (int i = 1; i <= 12; i++) { - valid_keys.push_back("F" + int_to_string(i)); + valid_keys.push_back('F' + int_to_string(i)); } valid_keys.push_back("`"); diff --git a/plugins/lua/buildingplan/inspectoroverlay.lua b/plugins/lua/buildingplan/inspectoroverlay.lua index 5262eccc8..1fcf19028 100644 --- a/plugins/lua/buildingplan/inspectoroverlay.lua +++ b/plugins/lua/buildingplan/inspectoroverlay.lua @@ -124,7 +124,7 @@ local function mouse_is_over_resume_button(rect) end function InspectorOverlay:onInput(keys) - if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding()) then + if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding(true)) then return false end if keys._MOUSE_L_DOWN and mouse_is_over_resume_button(self.frame_parent_rect) then @@ -136,7 +136,7 @@ function InspectorOverlay:onInput(keys) end function InspectorOverlay:render(dc) - if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding()) then + if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding(true)) then return end if reset_inspector_flag then diff --git a/plugins/misery.cpp b/plugins/misery.cpp index f241394ae..5e3bf6e1f 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -72,7 +72,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { DEBUG(config,out).print("initializing %s\n", plugin_name); - tailor_instance = dts::make_unique(); + tailor_instance = std::make_unique(); // provide a configuration interface for the plugin commands.push_back(PluginCommand( diff --git a/scripts b/scripts index ee8217978..fafe4677c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ee8217978d25bc8f9c3efa21156dd8ff99896f68 +Subproject commit fafe4677cd26d1915c417828368b6fe94e2caa95