Merge branch 'develop' of https://github.com/DFHack/dfhack into remote_reader

# Conflicts:
#	docs/changelog.txt
develop
JapaMala 2018-07-08 19:23:56 +05:30
commit 4b64d2dec6
120 changed files with 2698 additions and 7924 deletions

3
.gitmodules vendored

@ -13,3 +13,6 @@
[submodule "scripts2"] [submodule "scripts2"]
path = scripts path = scripts
url = ../../DFHack/scripts.git url = ../../DFHack/scripts.git
[submodule "depends/jsoncpp"]
path = depends/jsoncpp-sub
url = ../../open-source-parsers/jsoncpp.git

@ -17,7 +17,7 @@ default_flags = [
'-I','depends/protobuf', '-I','depends/protobuf',
'-I','depends/lua/include', '-I','depends/lua/include',
'-I','depends/md5', '-I','depends/md5',
'-I','depends/jsoncpp', '-I','depends/jsoncpp/include',
'-I','depends/tinyxml', '-I','depends/tinyxml',
'-I','depends/tthread', '-I','depends/tthread',
'-I','depends/clsocket/src', '-I','depends/clsocket/src',

@ -11,6 +11,7 @@ else(CMAKE_CONFIGURATION_TYPES)
endif(CMAKE_CONFIGURATION_TYPES) endif(CMAKE_CONFIGURATION_TYPES)
OPTION(BUILD_DOCS "Choose whether to build the documentation (requires python and Sphinx)." OFF) OPTION(BUILD_DOCS "Choose whether to build the documentation (requires python and Sphinx)." OFF)
OPTION(REMOVE_SYMBOLS_FROM_DF_STUBS "Remove debug symbols from DF stubs. (Reduces libdfhack size to about half but removes a few useful symbols)" ON)
## some generic CMake magic ## some generic CMake magic
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
@ -136,6 +137,21 @@ ${CMAKE_MODULE_PATH}
# generates compile_commands.json, used for autocompletion by some editors # generates compile_commands.json, used for autocompletion by some editors
SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) SET(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(CheckCXXSourceCompiles)
CHECK_CXX_SOURCE_COMPILES("
#include <cstdlib>
#include <cuchar>
int main(void) {
char32_t in = 0;
char out[MB_CUR_MAX];
std::mbstate_t state{};
std::c32rtomb(out, in, &state);
return 0;
}" HAVE_CUCHAR2)
if(HAVE_CUCHAR2)
add_definitions("-DHAVE_CUCHAR")
endif()
# mixing the build system with the source code is ugly and stupid. enforce the opposite :) # mixing the build system with the source code is ugly and stupid. enforce the opposite :)
if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(FATAL_ERROR "In-source builds are not allowed.") message(FATAL_ERROR "In-source builds are not allowed.")
@ -147,9 +163,9 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif() endif()
# set up versioning. # set up versioning.
set(DF_VERSION "0.44.10") set(DF_VERSION "0.44.12")
set(DFHACK_RELEASE "r1") set(DFHACK_RELEASE "alpha0")
set(DFHACK_PRERELEASE FALSE) set(DFHACK_PRERELEASE TRUE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
@ -348,7 +364,6 @@ find_package(ZLIB REQUIRED)
include_directories(depends/protobuf) include_directories(depends/protobuf)
include_directories(depends/lua/include) include_directories(depends/lua/include)
include_directories(depends/md5) include_directories(depends/md5)
include_directories(depends/jsoncpp)
# Support linking against external tinyxml # Support linking against external tinyxml
# If we find an external tinyxml, set the DFHACK_TINYXML variable to "tinyxml" # If we find an external tinyxml, set the DFHACK_TINYXML variable to "tinyxml"

@ -9,7 +9,9 @@ if(NOT TinyXML_FOUND)
endif() endif()
add_subdirectory(tthread) add_subdirectory(tthread)
add_subdirectory(jsoncpp) OPTION(JSONCPP_WITH_TESTS "Compile and (for jsoncpp_check) run JsonCpp test executables" OFF)
OPTION(JSONCPP_WITH_POST_BUILD_UNITTEST "Automatically run unit-tests as a post build step" OFF)
add_subdirectory(jsoncpp-sub)
# build clsocket static and only as a dependency. Setting those options here overrides its own default settings. # build clsocket static and only as a dependency. Setting those options here overrides its own default settings.
OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF)
OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON)

@ -0,0 +1 @@
Subproject commit ddabf50f72cf369bf652a95c4d9fe31a1865a781

@ -1,2 +0,0 @@
PROJECT(jsoncpp)
ADD_LIBRARY(jsoncpp STATIC jsoncpp.cpp)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -19,10 +19,11 @@ Antalia tamarakorr
Anuradha Dissanayake falconne Anuradha Dissanayake falconne
Atkana Atkana Atkana Atkana
AtomicChicken AtomicChicken AtomicChicken AtomicChicken
Bearskie Bearskie
belal jimhester belal jimhester
Ben Lubar BenLubar Ben Lubar BenLubar
Ben Rosser TC01 Ben Rosser TC01
brndd brndd brndd brndd burneddi
Bumber Bumber64 Bumber Bumber64
Caldfir caldfir Caldfir caldfir
Carter Bray Qartar Carter Bray Qartar
@ -58,6 +59,7 @@ jj jjyg jj``
John Beisley huin John Beisley huin
John Shade gsvslto John Shade gsvslto
Jonas Ask Jonas Ask
Josh Cooper cppcooper coope
kane-t kane-t kane-t kane-t
Kelly Kinkade ab9rf Kelly Kinkade ab9rf
Kris Parker kaypy Kris Parker kaypy

@ -74,6 +74,11 @@ Note that the ``:foo`` syntax for whitespace in arguments is not compatible \
with '+ args'. with '+ args'.
.. _dfhack-run:
dfhack-run
..........
If DF and DFHack are already running, calling ``dfhack-run my command`` If DF and DFHack are already running, calling ``dfhack-run my command``
in an external terminal is equivalent to calling ``my command`` in the in an external terminal is equivalent to calling ``my command`` in the
DFHack console. Direct use of the DFhack console is generally easier, DFHack console. Direct use of the DFhack console is generally easier,
@ -90,10 +95,10 @@ but ``dfhack-run`` can be useful in a variety of circumstances:
Examples:: Examples::
./dfhack-run cursecheck ./dfhack-run cursecheck
dfhack-run multicmd kill-lua; die dfhack-run kill-lua
The first (\*nix) example `checks for vampires <cursecheck>`; the The first (\*nix) example `checks for vampires <cursecheck>`; the
second (Windows) example uses `kill-lua` to cancel a script and exits. second (Windows) example uses `kill-lua` to stop a Lua script.
Built-in Commands Built-in Commands

@ -2404,6 +2404,10 @@ environment by the mandatory init file dfhack.lua:
If the argument is a lua table or DF object reference, prints all fields. If the argument is a lua table or DF object reference, prints all fields.
* ``printall_recurse(obj)``
If the argument is a lua table or DF object reference, prints all fields recursively.
* ``copyall(obj)`` * ``copyall(obj)``
Returns a shallow copy of the table or reference as a lua table. Returns a shallow copy of the table or reference as a lua table.
@ -3517,6 +3521,8 @@ The widget implements:
Same as with an ordinary list. Same as with an ordinary list.
.. _lua-plugins:
======= =======
Plugins Plugins
======= =======
@ -3901,6 +3907,127 @@ A class with all the tcp functionality.
Tries connecting to that address and port. Returns ``client`` object. Tries connecting to that address and port. Returns ``client`` object.
.. _cxxrandom:
cxxrandom
=========
Exposes some features of the C++11 random number library to Lua.
Native functions (exported to Lua)
----------------------------------
- ``GenerateEngine(seed)``
returns engine id
- ``DestroyEngine(rngID)``
destroys corresponding engine
- ``NewSeed(rngID, seed)``
re-seeds engine
- ``rollInt(rngID, min, max)``
generates random integer
- ``rollDouble(rngID, min, max)``
generates random double
- ``rollNormal(rngID, avg, stddev)``
generates random normal[gaus.]
- ``rollBool(rngID, chance)``
generates random boolean
- ``MakeNumSequence(start, end)``
returns sequence id
- ``AddToSequence(seqID, num)``
adds a number to the sequence
- ``ShuffleSequence(rngID, seqID)``
shuffles the number sequence
- ``NextInSequence(seqID)``
returns the next number in sequence
Lua plugin functions
--------------------
- ``MakeNewEngine(seed)``
returns engine id
Lua plugin classes
------------------
``crng``
~~~~~~~~
- ``init(id, df, dist)``: constructor
- ``id``: Reference ID of engine to use in RNGenerations
- ``df`` (optional): bool indicating whether to destroy the Engine when the crng object is garbage collected
- ``dist`` (optional): lua number distribution to use
- ``changeSeed(seed)``: alters engine's seed value
- ``setNumDistrib(distrib)``: sets the number distribution crng object should use
- ``distrib``: number distribution object to use in RNGenerations
- ``next()``: returns the next number in the distribution
- ``shuffle()``: effectively shuffles the number distribution
``normal_distribution``
~~~~~~~~~~~~~~~~~~~~~~~
- ``init(avg, stddev)``: constructor
- ``next(id)``: returns next number in the distribution
- ``id``: engine ID to pass to native function
``real_distribution``
~~~~~~~~~~~~~~~~~~~~~
- ``init(min, max)``: constructor
- ``next(id)``: returns next number in the distribution
- ``id``: engine ID to pass to native function
``int_distribution``
~~~~~~~~~~~~~~~~~~~~
- ``init(min, max)``: constructor
- ``next(id)``: returns next number in the distribution
- ``id``: engine ID to pass to native function
``bool_distribution``
~~~~~~~~~~~~~~~~~~~~~
- ``init(min, max)``: constructor
- ``next(id)``: returns next boolean in the distribution
- ``id``: engine ID to pass to native function
``num_sequence``
~~~~~~~~~~~~~~~~
- ``init(a, b)``: constructor
- ``add(num)``: adds num to the end of the number sequence
- ``shuffle()``: shuffles the sequence of numbers
- ``next()``: returns next number in the sequence
======= =======
Scripts Scripts

@ -2731,3 +2731,14 @@ can easily result in inconsistent state once this plugin is
available again. The effects may be as weird as negative power available again. The effects may be as weird as negative power
being generated. being generated.
=======
Lua API
=======
Some plugins consist solely of native libraries exposed to Lua. They are listed
in the `lua-api` file under `lua-plugins`:
* `eventful`
* `building-hacks`
* `luasocket`
* `cxxrandom`

@ -37,32 +37,146 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
================================================================================ ================================================================================
# Future # Future
## Fixes
- `stonesense`: fixed ``PLANT:DESERT_LIME:LEAF`` typo
## API
- Added C++-style linked list interface for DF linked lists
================================================================================
# 0.44.11-beta2.1
## Internals
-@ `stonesense`: fixed build
================================================================================
# 0.44.11-beta2
## Fixes
-@ Windows: Fixed console failing to initialize
- `command-prompt`: added support for commands that require a specific screen to be visible, e.g. `spotclean`
- `gui/workflow`: fixed advanced constraint menu for crafts
## API
- Added ``Screen::Hide`` to temporarily hide screens, like `command-prompt`
================================================================================
# 0.44.11-beta1
## Fixes
- Fixed displayed names (from ``Units::getVisibleName``) for units with identities
- Fixed potential memory leak in ``Screen::show()``
- `fix/dead-units`: fixed script trying to use missing isDiplomat function
## Misc Improvements
- Console:
- added support for multibyte characters on Linux/macOS
- made the console exit properly when an interactive command is active (`liquids`, `mode`, `tiletypes`)
- Made the ``DFHACK_PORT`` environment variable take priority over ``remote-server.json``
- Linux: added automatic support for GCC sanitizers in ``dfhack`` script
- `digfort`: added better map bounds checking
- `dfhack-run`: added support for port specified in ``remote-server.json``, to match DFHack's behavior
- `remove-stress`:
- added support for ``-all`` as an alternative to the existing ``all`` argument for consistency
- sped up significantly
- improved output/error messages
- now removes tantrums, depression, and obliviousness
- `ruby`: sped up handling of onupdate events
## API
- New functions:
- ``Units::isDiplomat(unit)``
- Exposed ``Screen::zoom()`` to C++ (was Lua-only)
## Lua
- Added ``printall_recurse`` to print tables and DF references recursively. It can be also used with ``^`` from the `lua` interpreter.
- ``gui.widgets``: ``List:setChoices`` clones ``choices`` for internal table changes
## Internals
- jsoncpp: updated to version 1.8.4 and switched to using a git submodule
## Structures
- ``history_event_entity_expels_hfst``: added (new in 0.44.11)
- ``history_event_site_surrenderedst``: added (new in 0.44.11)
- ``history_event_type``: added ``SITE_SURRENDERED``, ``ENTITY_EXPELS_HF`` (new in 0.44.11)
- ``syndrome``: identified a few fields
- ``viewscreen_civlistst``: fixed layout and identified many fields
================================================================================
# 0.44.11-alpha1
## Structures
- Dropped 0.44.10 support
- Added support for automatically sizing arrays indexed with an enum
- Removed stale generated CSV files and DT layouts from pre-0.43.05
- ``announcement_type``: new in 0.44.11: ``NEW_HOLDING``, ``NEW_MARKET_LINK``
- ``breath_attack_type``: added ``OTHER``
- ``historical_figure_info.relationships.list``: added ``unk_3a``-``unk_3c`` fields at end
- ``interface_key``: added bindings new in 0.44.11
- ``occupation_type``: new in 0.44.11: ``MESSENGER``
- ``profession``: new in 0.44.11: ``MESSENGER``
- ``ui_sidebar_menus``:
- ``unit.in_squad``: renamed to ``unit.squad_list_opened``, fixed location
- ``unit``: added ``expel_error`` and other unknown fields new in 0.44.11
- ``hospital``: added, new in 0.44.11
- ``num_speech_tokens``, ``unk_17d8``: moved out of ``command_line`` to fix layout on x64
-@ ``viewscreen_civlistst``: added a few new fields (incomplete)
- ``viewscreen_locationsst``: identified ``edit_input``
================================================================================
# 0.44.10-r2
## New Plugins
- `cxxrandom`: exposes some features of the C++11 random number library to Lua
## New Scripts
- `gui/stamper`: allows manipulation of designations by transforms such as translations, reflections, rotations, and inversion
- `add-recipe`: adds unknown crafting recipes to the player's civ
## Fixes ## Fixes
- Fixed many tools incorrectly using the ``dead`` unit flag (they should generally check ``flags2.killed`` instead) - Fixed many tools incorrectly using the ``dead`` unit flag (they should generally check ``flags2.killed`` instead)
- Fixed many tools passing incorrect arguments to printf-style functions, including a few possible crashes (`changelayer`, `follow`, `forceequip`, `generated-creature-renamer`)
- Fixed ``-g`` flag (GDB) in Linux ``dfhack`` script (particularly on x64) - Fixed ``-g`` flag (GDB) in Linux ``dfhack`` script (particularly on x64)
- Fixed several bugs in Lua scripts found by static analysis (df-luacheck)
- `autochop`, `autodump`, `autogems`, `automelt`, `autotrade`, `buildingplan`, `dwarfmonitor`, `fix-unit-occupancy`, `fortplan`, `stockflow`: fix issues with periodic tasks not working for some time after save/load cycles - `autochop`, `autodump`, `autogems`, `automelt`, `autotrade`, `buildingplan`, `dwarfmonitor`, `fix-unit-occupancy`, `fortplan`, `stockflow`: fix issues with periodic tasks not working for some time after save/load cycles
- `buildingplan`, `fortplan`: stop running before a world has fully loaded - `autogems`, `fix-unit-occupancy`: stopped running when a fort isn't loaded (e.g. while embarking)
- `buildingplan`, `fortplan`: stopped running before a world has fully loaded
- `autogems`: - `autogems`:
- stop running repeatedly when paused - stop running repeatedly when paused
- fixed crash when furnaces are linked to same stockpiles as jeweler's workshops - fixed crash when furnaces are linked to same stockpiles as jeweler's workshops
- `ban-cooking`: fixed errors introduced by kitchen structure changes in 0.44.10-r1 - `ban-cooking`: fixed errors introduced by kitchen structure changes in 0.44.10-r1
- `remove-stress`: fixed an error when running on soul-less units (e.g. with ``-all``) - `remove-stress`: fixed an error when running on soul-less units (e.g. with ``-all``)
- `revflood`: stopped revealing tiles adjacent to tiles above open space inappropriately - `revflood`: stopped revealing tiles adjacent to tiles above open space inappropriately
- `dig`: fixed "Inappropriate dig square" announcements if digging job has been posted
- `stockpiles`: ``loadstock`` now sets usable and unusable weapon and armor settings
- `stocks`: stopped listing carried items under stockpiles where they were picked up from
- `deramp`: fixed deramp to find designations that already have jobs posted
- `fixnaked`: fixed errors due to emotion changes in 0.44
- `autounsuspend`: now skips planned buildings
## Misc Improvements ## Misc Improvements
- Added script name to messages produced by ``qerror()`` in Lua scripts - Added script name to messages produced by ``qerror()`` in Lua scripts
- Fixed an issue in around 30 scripts that could prevent edits to the files (adding valid arguments) from taking effect - Fixed an issue in around 30 scripts that could prevent edits to the files (adding valid arguments) from taking effect
- Linux: Added several new options to ``dfhack`` script: ``--remotegdb``, ``--gdbserver``, ``--strace`` - Linux: Added several new options to ``dfhack`` script: ``--remotegdb``, ``--gdbserver``, ``--strace``
- `bodyswap`: improved error handling
- `buildingplan`: added max quality setting - `buildingplan`: added max quality setting
- `caravan`: documented (new in 0.44.10-alpha1) - `caravan`: documented (new in 0.44.10-alpha1)
- `deathcause`: added "slaughtered" to descriptions
- `fix/retrieve-units`: now re-adds units to active list to counteract `fix/dead-units` - `fix/retrieve-units`: now re-adds units to active list to counteract `fix/dead-units`
- `item-descriptions`: fixed several grammatical errors
- `modtools/create-unit`:
- added quantity argument
- now selects a caste at random if none is specified
- `mousequery`: - `mousequery`:
- migrated several features from TWBT's fork - migrated several features from TWBT's fork
- added ability to drag with left/right buttons - added ability to drag with left/right buttons
- added depth display for TWBT (when multilevel is enabled) - added depth display for TWBT (when multilevel is enabled)
- made shift+click jump to lower levels visible with TWBT - made shift+click jump to lower levels visible with TWBT
- `title-version`: added version to options screen too - `title-version`: added version to options screen too
- `remotefortressreader`: added build menu information - `embark-assistant`:
- changed region interaction matching to search for evil rain, syndrome rain, and reanimation rather than interaction presence (misleadingly called evil weather), reanimation, and thralling
- gave syndrome rain and reanimation wider ranges of criterion values
- `fix/dead-units`: added a delay of around 1 month before removing units
## API ## API
- New functions (also exposed to Lua): - New functions (also exposed to Lua):
@ -75,10 +189,22 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- Added ``profiler`` module to measure lua performance - Added ``profiler`` module to measure lua performance
- Enabled shift+cursor movement in WorkshopOverlay-derived screens - Enabled shift+cursor movement in WorkshopOverlay-derived screens
## Structures
- ``unit_flags1``: renamed ``dead`` to ``inactive`` to better reflect its use
- ``item_body_component``: fixed location of ``corpse_flags``
- ``job_type``: added ``is_designation`` attribute
- ``unit_thought_type``: added ``SawDeadBody`` (new in 0.44.10)
- ``unit_personality``: fixed location of ``current_focus`` and ``undistracted_focus``
- ``incident_sub6_performance``: identified some fields
- ``job_handler``: fixed static array layout
## Internals ## Internals
- Added fallback for YouCompleteMe database lookup failures (e.g. for newly-created files) - Added fallback for YouCompleteMe database lookup failures (e.g. for newly-created files)
- jsoncpp: fixed constructor with ``long`` on Linux - jsoncpp: fixed constructor with ``long`` on Linux
- Improved efficiency and error handling in ``stl_vsprintf`` and related functions
- Added build option to generate symbols for large generated files containing df-structures metadata
================================================================================
# 0.44.10-r1 # 0.44.10-r1
## New Scripts ## New Scripts
@ -134,6 +260,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- ``viewscreen_kitchenprefst.forbidden``, ``possible``: now a bitfield, ``kitchen_pref_flag`` - ``viewscreen_kitchenprefst.forbidden``, ``possible``: now a bitfield, ``kitchen_pref_flag``
- ``world_data.feature_map``: added extensive documentation (in XML) - ``world_data.feature_map``: added extensive documentation (in XML)
================================================================================
# 0.44.10-beta1 # 0.44.10-beta1
## New Scripts ## New Scripts
@ -167,6 +294,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- Added function names to DFHack's NullPointer and InvalidArgument exceptions - Added function names to DFHack's NullPointer and InvalidArgument exceptions
- Linux: required plugins to have symbols resolved at link time, for consistency with other platforms - Linux: required plugins to have symbols resolved at link time, for consistency with other platforms
================================================================================
# 0.44.10-alpha1 # 0.44.10-alpha1
## New Scripts ## New Scripts
@ -174,6 +302,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `gui/autogems`: a configuration UI for the `autogems` plugin - `gui/autogems`: a configuration UI for the `autogems` plugin
## Fixes ## Fixes
- Fixed uninitialized pointer being returned from ``Gui::getAnyUnit()`` in rare cases
- `autohauler`, `autolabor`, `labormanager`: fixed fencepost error and potential crash
- `dwarfvet`: fixed infinite loop if an animal is not accepted at a hospital
- `liquids`: fixed "range" command to default to 1 for dimensions consistently - `liquids`: fixed "range" command to default to 1 for dimensions consistently
- `search`: fixed 4/6 keys in unit screen search - `search`: fixed 4/6 keys in unit screen search
- `view-item-info`: fixed an error with some armor - `view-item-info`: fixed an error with some armor

@ -15,9 +15,9 @@ CHANGELOG_SECTIONS = [
'Removed', 'Removed',
'API', 'API',
'Internals', 'Internals',
'Structures',
'Lua', 'Lua',
'Ruby', 'Ruby',
'Structures',
] ]
REPLACEMENTS = { REPLACEMENTS = {

@ -267,14 +267,15 @@ ADD_CUSTOM_COMMAND(
) )
ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml) ADD_CUSTOM_TARGET(generate_headers DEPENDS ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml)
IF(REMOVE_SYMBOLS_FROM_DF_STUBS)
IF(UNIX) IF(UNIX)
# Don't produce debug info for generated stubs # Don't produce debug info for generated stubs
SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp ${STATIC_FIELDS_FILES} SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp ${STATIC_FIELDS_FILES}
PROPERTIES COMPILE_FLAGS "-g0 -O1") PROPERTIES COMPILE_FLAGS "-g0 -O1")
ELSE(WIN32) ELSE(WIN32)
SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp ${STATIC_FIELDS_FILES} SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp ${STATIC_FIELDS_FILES}
PROPERTIES COMPILE_FLAGS "/O1 /bigobj") PROPERTIES COMPILE_FLAGS "/O1 /bigobj")
ENDIF()
ENDIF() ENDIF()
# Compilation # Compilation
@ -365,10 +366,10 @@ IF(APPLE)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
ENDIF() ENDIF()
TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsoncpp dfhack-version ${PROJECT_LIBS}) TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua jsoncpp_lib_static dfhack-version ${PROJECT_LIBS})
SET_TARGET_PROPERTIES(dfhack PROPERTIES INTERFACE_LINK_LIBRARIES "") SET_TARGET_PROPERTIES(dfhack PROPERTIES INTERFACE_LINK_LIBRARIES "")
TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket jsoncpp_lib_static)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
if(APPLE) if(APPLE)

@ -60,6 +60,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <termios.h> #include <termios.h>
#include <errno.h> #include <errno.h>
#include <deque> #include <deque>
#ifdef HAVE_CUCHAR
#include <cuchar>
#else
#include <cwchar>
#endif
// George Vulov for MacOSX // George Vulov for MacOSX
#ifndef __LINUX__ #ifndef __LINUX__
@ -133,6 +138,68 @@ const char * getANSIColor(const int c)
} }
} }
#ifdef HAVE_CUCHAR
// Use u32string for GCC 6 and later and msvc to allow potable implementation
using u32string = std::u32string;
using std::c32rtomb;
using std::mbrtoc32;
#else
// Fallback for gcc 4 and 5 that don't have cuchar header
// But wchar_t is 4 bytes that is a good fallback implementation
using u32string = std::wstring;
size_t mbrtoc32(u32string::value_type* c,
const char* s,
std::size_t n,
std::mbstate_t* ps)
{
return std::mbrtowc(c, s, n, ps);
}
size_t c32rtomb(char* mb,
u32string::value_type c,
std::mbstate_t* ps)
{
return std::wcrtomb(mb, c, ps);
}
#endif
//! Convert a locale defined multibyte coding to UTF-32 string for easier
//! character processing.
static u32string fromLocaleMB(const std::string& str)
{
u32string rv;
u32string::value_type ch;
size_t pos = 0;
ssize_t sz;
std::mbstate_t state{};
while ((sz = mbrtoc32(&ch,&str[pos], str.size() - pos, &state)) != 0) {
if (sz == -1 || sz == -2)
break;
rv.push_back(ch);
if (sz == -3) /* multi value character */
continue;
pos += sz;
}
return rv;
}
//! Convert a UTF-32 string back to locale defined multibyte coding.
static std::string toLocaleMB(const u32string& wstr)
{
std::stringstream ss{};
char mb[MB_CUR_MAX];
std::mbstate_t state{};
const size_t err = -1;
for (auto ch: wstr) {
size_t sz = c32rtomb(mb, ch, &state);
if (sz == err)
break;
ss.write(mb, sz);
}
return ss.str();
}
namespace DFHack namespace DFHack
{ {
class Private class Private
@ -157,7 +224,7 @@ namespace DFHack
FD_SET(STDIN_FILENO, &descriptor_set); FD_SET(STDIN_FILENO, &descriptor_set);
FD_SET(exit_pipe[0], &descriptor_set); FD_SET(exit_pipe[0], &descriptor_set);
int ret = TMP_FAILURE_RETRY( int ret = TMP_FAILURE_RETRY(
select (FD_SETSIZE,&descriptor_set, NULL, NULL, NULL) select (std::max(STDIN_FILENO,exit_pipe[0])+1,&descriptor_set, NULL, NULL, NULL)
); );
if(ret == -1) if(ret == -1)
return false; return false;
@ -356,15 +423,15 @@ namespace DFHack
int count; int count;
if (enable_raw() == -1) return 0; if (enable_raw() == -1) return 0;
if(state == con_lineedit) if(state == con_lineedit)
return -1; return Console::FAILURE;
state = con_lineedit; state = con_lineedit;
count = prompt_loop(lock,ch); count = prompt_loop(lock,ch);
state = con_unclaimed; state = con_unclaimed;
disable_raw(); disable_raw();
print("\n"); print("\n");
if(count != -1) if(count > Console::FAILURE)
{ {
output = raw_buffer; output = toLocaleMB(raw_buffer);
} }
return count; return count;
} }
@ -374,9 +441,9 @@ namespace DFHack
struct termios raw; struct termios raw;
if (!supported_terminal) if (!supported_terminal)
return -1; return Console::FAILURE;
if (tcgetattr(STDIN_FILENO,&orig_termios) == -1) if (tcgetattr(STDIN_FILENO,&orig_termios) == -1)
return -1; return Console::FAILURE;
raw = orig_termios; //modify the original mode raw = orig_termios; //modify the original mode
// input modes: no break, no CR to NL, no parity check, no strip char, // input modes: no break, no CR to NL, no parity check, no strip char,
@ -398,7 +465,7 @@ namespace DFHack
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;// 1 byte, no timer raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;// 1 byte, no timer
// put terminal in raw mode // put terminal in raw mode
if (tcsetattr(STDIN_FILENO, TCSADRAIN, &raw) < 0) if (tcsetattr(STDIN_FILENO, TCSADRAIN, &raw) < 0)
return -1; return Console::FAILURE;
rawmode = 1; rawmode = 1;
return 0; return 0;
} }
@ -414,26 +481,24 @@ namespace DFHack
char seq[64]; char seq[64];
int cols = get_columns(); int cols = get_columns();
int plen = prompt.size(); int plen = prompt.size();
const char * buf = raw_buffer.c_str();
int len = raw_buffer.size(); int len = raw_buffer.size();
int begin = 0;
int cooked_cursor = raw_cursor; int cooked_cursor = raw_cursor;
// Use math! This is silly. if ((plen+cooked_cursor) >= cols)
while((plen+cooked_cursor) >= cols)
{
buf++;
len--;
cooked_cursor--;
}
while (plen+len > cols)
{ {
len--; begin = plen+cooked_cursor-cols-1;
len -= plen+cooked_cursor-cols-1;
cooked_cursor -= plen+cooked_cursor-cols-1;
} }
if (plen+len > cols)
len -= plen+len - cols;
std::string mbstr = toLocaleMB(raw_buffer.substr(begin,len));
/* Cursor to left edge */ /* Cursor to left edge */
snprintf(seq,64,"\x1b[1G"); snprintf(seq,64,"\x1b[1G");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
/* Write the prompt and the current buffer content */ /* Write the prompt and the current buffer content */
if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return; if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return;
if (::write(STDIN_FILENO,buf,len) == -1) return; if (::write(STDIN_FILENO,mbstr.c_str(),mbstr.length()) == -1) return;
/* Erase to right */ /* Erase to right */
snprintf(seq,64,"\x1b[0K"); snprintf(seq,64,"\x1b[0K");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
@ -453,7 +518,8 @@ namespace DFHack
* initially is just an empty string. */ * initially is just an empty string. */
const std::string empty; const std::string empty;
history.add(empty); history.add(empty);
if (::write(fd,prompt.c_str(),prompt.size()) == -1) return -1; if (::write(fd,prompt.c_str(),prompt.size()) == -1)
return Console::FAILURE;
while(1) while(1)
{ {
unsigned char c; unsigned char c;
@ -463,7 +529,7 @@ namespace DFHack
if(!read_char(c)) if(!read_char(c))
{ {
lock->lock(); lock->lock();
return -2; return Console::SHUTDOWN;
} }
lock->lock(); lock->lock();
/* Only autocomplete when the callback is set. It returns < 0 when /* Only autocomplete when the callback is set. It returns < 0 when
@ -496,8 +562,7 @@ namespace DFHack
history.remove(); history.remove();
return raw_buffer.size(); return raw_buffer.size();
case 3: // ctrl-c case 3: // ctrl-c
errno = EAGAIN; return Console::RETRY;
return -1;
case 127: // backspace case 127: // backspace
case 8: // ctrl-h case 8: // ctrl-h
if (raw_cursor > 0 && raw_buffer.size() > 0) if (raw_cursor > 0 && raw_buffer.size() > 0)
@ -512,7 +577,7 @@ namespace DFHack
if (!read_char(seq[0])) if (!read_char(seq[0]))
{ {
lock->lock(); lock->lock();
return -2; return Console::SHUTDOWN;
} }
lock->lock(); lock->lock();
if (seq[0] == 'b') if (seq[0] == 'b')
@ -528,7 +593,7 @@ namespace DFHack
if (!read_char(seq[1])) if (!read_char(seq[1]))
{ {
lock->lock(); lock->lock();
return -2; return Console::SHUTDOWN;
} }
if (seq[1] == 'D') if (seq[1] == 'D')
{ {
@ -555,7 +620,7 @@ namespace DFHack
{ {
/* Update the current history entry before to /* Update the current history entry before to
* overwrite it with tne next one. */ * overwrite it with tne next one. */
history[history_index] = raw_buffer; history[history_index] = toLocaleMB(raw_buffer);
/* Show the new entry */ /* Show the new entry */
history_index += (seq[1] == 'A') ? 1 : -1; history_index += (seq[1] == 'A') ? 1 : -1;
if (history_index < 0) if (history_index < 0)
@ -568,7 +633,7 @@ namespace DFHack
history_index = history.size()-1; history_index = history.size()-1;
break; break;
} }
raw_buffer = history[history_index]; raw_buffer = fromLocaleMB(history[history_index]);
raw_cursor = raw_buffer.size(); raw_cursor = raw_buffer.size();
prompt_refresh(); prompt_refresh();
} }
@ -593,7 +658,7 @@ namespace DFHack
if(!read_char(seq2)) if(!read_char(seq2))
{ {
lock->lock(); lock->lock();
return -2; return Console::SHUTDOWN;
} }
lock->lock(); lock->lock();
if (seq[1] == '3' && seq2 == '~' ) if (seq[1] == '3' && seq2 == '~' )
@ -609,7 +674,7 @@ namespace DFHack
if (!read_char(seq3[0]) || !read_char(seq3[1])) if (!read_char(seq3[0]) || !read_char(seq3[1]))
{ {
lock->lock(); lock->lock();
return -2; return Console::SHUTDOWN;
} }
if (seq2 == ';') if (seq2 == ';')
{ {
@ -672,15 +737,31 @@ namespace DFHack
default: default:
if (c >= 32) // Space if (c >= 32) // Space
{ {
u32string::value_type c32;
char mb[MB_CUR_MAX];
size_t count = 1;
mb[0] = c;
ssize_t sz;
std::mbstate_t state{};
// Read all bytes belonging to a multi byte
// character starting from the first bye already red
while ((sz = mbrtoc32(&c32,&mb[count-1],1, &state)) < 0) {
if (sz == -1 || sz == -3)
return Console::FAILURE; /* mbrtoc32 error (not valid utf-32 character */
if(!read_char(c))
return Console::SHUTDOWN;
mb[count++] = c;
}
if (raw_buffer.size() == size_t(raw_cursor)) if (raw_buffer.size() == size_t(raw_cursor))
{ {
raw_buffer.append(1,c); raw_buffer.append(1,c32);
raw_cursor++; raw_cursor++;
if (plen+raw_buffer.size() < size_t(get_columns())) if (plen+raw_buffer.size() < size_t(get_columns()))
{ {
/* Avoid a full update of the line in the /* Avoid a full update of the line in the
* trivial case. */ * trivial case. */
if (::write(fd,&c,1) == -1) return -1; if (::write(fd,mb,count) == -1)
return Console::FAILURE;
} }
else else
{ {
@ -689,7 +770,7 @@ namespace DFHack
} }
else else
{ {
raw_buffer.insert(raw_cursor,1,c); raw_buffer.insert(raw_cursor,1,c32);
raw_cursor++; raw_cursor++;
prompt_refresh(); prompt_refresh();
} }
@ -712,8 +793,8 @@ namespace DFHack
} state; } state;
bool in_batch; bool in_batch;
std::string prompt; // current prompt string std::string prompt; // current prompt string
std::string raw_buffer; // current raw mode buffer u32string raw_buffer; // current raw mode buffer
std::string yank_buffer; // last text deleted with Ctrl-K/Ctrl-U u32string yank_buffer; // last text deleted with Ctrl-K/Ctrl-U
int raw_cursor; // cursor position in the buffer int raw_cursor; // cursor position in the buffer
// thread exit mechanism // thread exit mechanism
int exit_pipe[2]; int exit_pipe[2];
@ -730,8 +811,7 @@ Console::Console()
} }
Console::~Console() Console::~Console()
{ {
if(inited) assert(!inited);
shutdown();
if(wlock) if(wlock)
delete wlock; delete wlock;
if(d) if(d)
@ -768,11 +848,6 @@ bool Console::shutdown(void)
if(!d) if(!d)
return true; return true;
lock_guard <recursive_mutex> g(*wlock); lock_guard <recursive_mutex> g(*wlock);
if(d->rawmode)
d->disable_raw();
d->print("\n");
inited = false;
// kill the thing
close(d->exit_pipe[1]); close(d->exit_pipe[1]);
return true; return true;
} }
@ -814,7 +889,7 @@ void Console::add_text(color_value color, const std::string &text)
int Console::get_columns(void) int Console::get_columns(void)
{ {
lock_guard <recursive_mutex> g(*wlock); lock_guard <recursive_mutex> g(*wlock);
int ret = -1; int ret = Console::FAILURE;
if(inited) if(inited)
ret = d->get_columns(); ret = d->get_columns();
return ret; return ret;
@ -823,7 +898,7 @@ int Console::get_columns(void)
int Console::get_rows(void) int Console::get_rows(void)
{ {
lock_guard <recursive_mutex> g(*wlock); lock_guard <recursive_mutex> g(*wlock);
int ret = -1; int ret = Console::FAILURE;
if(inited) if(inited)
ret = d->get_rows(); ret = d->get_rows();
return ret; return ret;
@ -853,9 +928,17 @@ void Console::cursor(bool enable)
int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch) int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch)
{ {
lock_guard <recursive_mutex> g(*wlock); lock_guard <recursive_mutex> g(*wlock);
int ret = -2; int ret = Console::SHUTDOWN;
if(inited) if(inited) {
ret = d->lineedit(prompt,output,wlock,ch); ret = d->lineedit(prompt,output,wlock,ch);
if (ret == Console::SHUTDOWN) {
// kill the thing
if(d->rawmode)
d->disable_raw();
d->print("\n");
inited = false;
}
}
return ret; return ret;
} }

@ -285,7 +285,10 @@ namespace DFHack
INPUT_RECORD rec; INPUT_RECORD rec;
DWORD count; DWORD count;
lock->unlock(); lock->unlock();
ReadConsoleInputA(console_in, &rec, 1, &count); if (ReadConsoleInputA(console_in, &rec, 1, &count) == 0) {
lock->lock();
return Console::SHUTDOWN;
}
lock->lock(); lock->lock();
if (rec.EventType != KEY_EVENT || !rec.Event.KeyEvent.bKeyDown) if (rec.EventType != KEY_EVENT || !rec.Event.KeyEvent.bKeyDown)
continue; continue;
@ -379,7 +382,7 @@ namespace DFHack
state = con_lineedit; state = con_lineedit;
this->prompt = prompt; this->prompt = prompt;
count = prompt_loop(lock, ch); count = prompt_loop(lock, ch);
if(count != -1) if(count > Console::FAILURE)
output = raw_buffer; output = raw_buffer;
state = con_unclaimed; state = con_unclaimed;
print("\n"); print("\n");
@ -588,7 +591,7 @@ void Console::cursor(bool enable)
int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch) int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch)
{ {
wlock->lock(); wlock->lock();
int ret = -2; int ret = Console::SHUTDOWN;
if(inited) if(inited)
ret = d->lineedit(prompt,output,wlock,ch); ret = d->lineedit(prompt,output,wlock,ch);
wlock->unlock(); wlock->unlock();

@ -77,7 +77,9 @@ using namespace DFHack;
#include <iomanip> #include <iomanip>
#include <stdlib.h> #include <stdlib.h>
#include <fstream> #include <fstream>
#include "tinythread.h" #include <thread>
#include <mutex>
#include <condition_variable>
#include "md5wrapper.h" #include "md5wrapper.h"
#include "SDL_events.h" #include "SDL_events.h"
@ -86,7 +88,6 @@ using namespace DFHack;
#include <dlfcn.h> #include <dlfcn.h>
#endif #endif
using namespace tthread;
using namespace df::enums; using namespace df::enums;
using df::global::init; using df::global::init;
using df::global::world; using df::global::world;
@ -96,48 +97,41 @@ using df::global::world;
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL); static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL);
size_t loadScriptFiles(Core* core, color_ostream& out, const vector<std::string>& prefix, const std::string& folder); size_t loadScriptFiles(Core* core, color_ostream& out, const vector<std::string>& prefix, const std::string& folder);
struct Core::Cond namespace DFHack {
{ struct MainThread {
Cond() //! MainThread::suspend keeps the main DF thread suspended from Core::Init to
{ //! thread exit.
predicate = false; static CoreSuspenderBase& suspend() {
wakeup = new tthread::condition_variable(); static thread_local CoreSuspenderBase lock(std::defer_lock);
} return lock;
~Cond()
{
delete wakeup;
}
bool Lock(tthread::mutex * m)
{
while(!predicate)
{
wakeup->wait(*m);
}
predicate = false;
return true;
}
bool Unlock()
{
predicate = true;
wakeup->notify_one();
return true;
} }
tthread::condition_variable * wakeup;
bool predicate;
}; };
}
struct Core::Private CoreSuspendReleaseMain::CoreSuspendReleaseMain()
{ {
tthread::mutex AccessMutex; MainThread::suspend().unlock();
tthread::mutex StackMutex; }
std::stack<Core::Cond*> suspended_tools;
Core::Cond core_cond;
thread::id df_suspend_thread;
int df_suspend_depth;
Private() { CoreSuspendReleaseMain::~CoreSuspendReleaseMain()
df_suspend_depth = 0; {
} MainThread::suspend().lock();
}
CoreSuspendClaimMain::CoreSuspendClaimMain()
{
MainThread::suspend().lock();
}
CoreSuspendClaimMain::~CoreSuspendClaimMain()
{
MainThread::suspend().unlock();
}
struct Core::Private
{
std::thread iothread;
std::thread hotkeythread;
}; };
struct CommandDepthCounter struct CommandDepthCounter
@ -227,9 +221,10 @@ void fHKthread(void * iodata)
cerr << "Hotkey thread has croaked." << endl; cerr << "Hotkey thread has croaked." << endl;
return; return;
} }
while(1) bool keep_going = true;
while(keep_going)
{ {
std::string stuff = core->getHotkeyCmd(); // waits on mutex! std::string stuff = core->getHotkeyCmd(keep_going); // waits on mutex!
if(!stuff.empty()) if(!stuff.empty())
{ {
color_ostream_proxy out(core->getConsole()); color_ostream_proxy out(core->getConsole());
@ -510,7 +505,7 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
bool Core::addScriptPath(string path, bool search_before) bool Core::addScriptPath(string path, bool search_before)
{ {
lock_guard<mutex> lock(*script_path_mutex); lock_guard<mutex> lock(script_path_mutex);
vector<string> &vec = script_paths[search_before ? 0 : 1]; vector<string> &vec = script_paths[search_before ? 0 : 1];
if (std::find(vec.begin(), vec.end(), path) != vec.end()) if (std::find(vec.begin(), vec.end(), path) != vec.end())
return false; return false;
@ -522,7 +517,7 @@ bool Core::addScriptPath(string path, bool search_before)
bool Core::removeScriptPath(string path) bool Core::removeScriptPath(string path)
{ {
lock_guard<mutex> lock(*script_path_mutex); lock_guard<mutex> lock(script_path_mutex);
bool found = false; bool found = false;
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
@ -541,7 +536,7 @@ bool Core::removeScriptPath(string path)
void Core::getScriptPaths(std::vector<std::string> *dest) void Core::getScriptPaths(std::vector<std::string> *dest)
{ {
lock_guard<mutex> lock(*script_path_mutex); lock_guard<mutex> lock(script_path_mutex);
dest->clear(); dest->clear();
string df_path = this->p->getPath(); string df_path = this->p->getPath();
for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it) for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it)
@ -1339,7 +1334,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n",
first.c_str()); first.c_str());
else if (p->size()) else if (p->size())
con.printerr("that implements %i commands - see \"ls %s\" for details\n", con.printerr("that implements %zi commands - see \"ls %s\" for details\n",
p->size(), first.c_str()); p->size(), first.c_str());
else else
con.printerr("but does not implement any commands\n"); con.printerr("but does not implement any commands\n");
@ -1449,13 +1444,15 @@ void fIOthread(void * iodata)
while (true) while (true)
{ {
string command = ""; string command = "";
int ret = con.lineedit("[DFHack]# ",command, main_history); int ret;
if(ret == -2) while ((ret = con.lineedit("[DFHack]# ",command, main_history))
== Console::RETRY);
if(ret == Console::SHUTDOWN)
{ {
cerr << "Console is shutting down properly." << endl; cerr << "Console is shutting down properly." << endl;
return; return;
} }
else if(ret == -1) else if(ret == Console::FAILURE)
{ {
cerr << "Console caught an unspecified error." << endl; cerr << "Console caught an unspecified error." << endl;
continue; continue;
@ -1480,10 +1477,33 @@ void fIOthread(void * iodata)
} }
} }
Core::Core() Core::~Core()
{ {
d = new Private(); if (MainThread::suspend().owns_lock())
MainThread::suspend().unlock();
if (d->hotkeythread.joinable()) {
std::lock_guard<std::mutex> lock(HotkeyMutex);
hotkey_set = SHUTDOWN;
HotkeyCond.notify_one();
}
if (d->iothread.joinable())
con.shutdown();
delete d;
}
Core::Core() :
d{new Private},
script_path_mutex{},
HotkeyMutex{},
HotkeyCond{},
alias_mutex{},
misc_data_mutex{},
CoreSuspendMutex{},
CoreWakeup{},
ownerThread{},
toolCount{0}
{
// init the console. This must be always the first step! // init the console. This must be always the first step!
plug_mgr = 0; plug_mgr = 0;
vif = 0; vif = 0;
@ -1494,11 +1514,7 @@ Core::Core()
memset(&(s_mods), 0, sizeof(s_mods)); memset(&(s_mods), 0, sizeof(s_mods));
// set up hotkey capture // set up hotkey capture
hotkey_set = false; hotkey_set = NO;
HotkeyMutex = 0;
HotkeyCond = 0;
alias_mutex = 0;
misc_data_mutex = 0;
last_world_data_ptr = NULL; last_world_data_ptr = NULL;
last_local_map_ptr = NULL; last_local_map_ptr = NULL;
last_pause_state = false; last_pause_state = false;
@ -1508,7 +1524,6 @@ Core::Core()
color_ostream::log_errors_to_stderr = true; color_ostream::log_errors_to_stderr = true;
script_path_mutex = new mutex();
}; };
void Core::fatal (std::string output) void Core::fatal (std::string output)
@ -1552,6 +1567,10 @@ bool Core::Init()
if(errorstate) if(errorstate)
return false; return false;
// Lock the CoreSuspendMutex until the thread exits or call Core::Shutdown
// Core::Update will temporary unlock when there is any commands queued
MainThread::suspend().lock();
// Re-route stdout and stderr again - DF seems to set up stdout and // Re-route stdout and stderr again - DF seems to set up stdout and
// stderr.txt on Windows as of 0.43.05. Also, log before switching files to // stderr.txt on Windows as of 0.43.05. Also, log before switching files to
// make it obvious what's going on if someone checks the *.txt files. // make it obvious what's going on if someone checks the *.txt files.
@ -1642,7 +1661,6 @@ bool Core::Init()
// Init global object pointers // Init global object pointers
df::global::InitGlobals(); df::global::InitGlobals();
alias_mutex = new recursive_mutex();
cerr << "Initializing Console.\n"; cerr << "Initializing Console.\n";
// init the console. // init the console.
@ -1732,7 +1750,6 @@ bool Core::Init()
} }
// create mutex for syncing with interactive tasks // create mutex for syncing with interactive tasks
misc_data_mutex=new mutex();
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
@ -1741,27 +1758,21 @@ bool Core::Init()
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;
HotkeyMutex = new mutex();
HotkeyCond = new condition_variable();
if (!is_text_mode || is_headless) if (!is_text_mode || is_headless)
{ {
cerr << "Starting IO thread.\n"; cerr << "Starting IO thread.\n";
// create IO thread // create IO thread
thread * IO = new thread(fIOthread, (void *) temp); d->iothread = std::thread{fIOthread, (void*)temp};
(void)IO;
} }
else else
{ {
cerr << "Starting dfhack.init thread.\n"; std::cerr << "Starting dfhack.init thread.\n";
thread * init = new thread(fInitthread, (void *) temp); d->iothread = std::thread{fInitthread, (void*)temp};
(void)init;
} }
cerr << "Starting DF input capture thread.\n"; cerr << "Starting DF input capture thread.\n";
// set up hotkey capture // set up hotkey capture
thread * HK = new thread(fHKthread, (void *) temp); d->hotkeythread = std::thread(fHKthread, (void *) temp);
(void)HK;
screen_window = new Windows::top_level_window(); screen_window = new Windows::top_level_window();
screen_window->addChild(new Windows::dfhack_dummy(5,10)); screen_window->addChild(new Windows::dfhack_dummy(5,10));
started = true; started = true;
@ -1775,7 +1786,7 @@ bool Core::Init()
if (df::global::ui_sidebar_menus) if (df::global::ui_sidebar_menus)
{ {
vector<string> args; vector<string> args;
const string & raw = df::global::ui_sidebar_menus->command_line.raw; const string & raw = df::global::ui_sidebar_menus->command_line.original;
size_t offset = 0; size_t offset = 0;
while (offset < raw.size()) while (offset < raw.size())
{ {
@ -1840,28 +1851,25 @@ bool Core::Init()
bool Core::setHotkeyCmd( std::string cmd ) bool Core::setHotkeyCmd( std::string cmd )
{ {
// access command // access command
HotkeyMutex->lock(); std::lock_guard<std::mutex> lock(HotkeyMutex);
{ hotkey_set = SET;
hotkey_set = true; hotkey_cmd = cmd;
hotkey_cmd = cmd; HotkeyCond.notify_all();
HotkeyCond->notify_all();
}
HotkeyMutex->unlock();
return true; return true;
} }
/// removes the hotkey command and gives it to the caller thread /// removes the hotkey command and gives it to the caller thread
std::string Core::getHotkeyCmd( void ) std::string Core::getHotkeyCmd( bool &keep_going )
{ {
string returner; string returner;
HotkeyMutex->lock(); std::unique_lock<std::mutex> lock(HotkeyMutex);
while ( ! hotkey_set ) HotkeyCond.wait(lock, [this]() -> bool {return this->hotkey_set;});
{ if (hotkey_set == SHUTDOWN) {
HotkeyCond->wait(*HotkeyMutex); keep_going = false;
return returner;
} }
hotkey_set = false; hotkey_set = NO;
returner = hotkey_cmd; returner = hotkey_cmd;
hotkey_cmd.clear(); hotkey_cmd.clear();
HotkeyMutex->unlock();
return returner; return returner;
} }
@ -1887,82 +1895,29 @@ void Core::printerr(const char *format, ...)
void Core::RegisterData( void *p, std::string key ) void Core::RegisterData( void *p, std::string key )
{ {
misc_data_mutex->lock(); std::lock_guard<std::mutex> lock(misc_data_mutex);
misc_data_map[key] = p; misc_data_map[key] = p;
misc_data_mutex->unlock();
} }
void *Core::GetData( std::string key ) void *Core::GetData( std::string key )
{ {
misc_data_mutex->lock(); std::lock_guard<std::mutex> lock(misc_data_mutex);
std::map<std::string,void*>::iterator it=misc_data_map.find(key); std::map<std::string,void*>::iterator it=misc_data_map.find(key);
if ( it != misc_data_map.end() ) if ( it != misc_data_map.end() )
{ {
void *p=it->second; void *p=it->second;
misc_data_mutex->unlock();
return p; return p;
} }
else else
{ {
misc_data_mutex->unlock();
return 0;// or throw an error. return 0;// or throw an error.
} }
} }
bool Core::isSuspended(void) bool Core::isSuspended(void)
{ {
lock_guard<mutex> lock(d->AccessMutex); return ownerThread.load() == std::this_thread::get_id();
return (d->df_suspend_depth > 0 && d->df_suspend_thread == this_thread::get_id());
}
void Core::Suspend()
{
auto tid = this_thread::get_id();
// If recursive, just increment the count
{
lock_guard<mutex> lock(d->AccessMutex);
if (d->df_suspend_depth > 0 && d->df_suspend_thread == tid)
{
d->df_suspend_depth++;
return;
}
}
// put the condition on a stack
Core::Cond *nc = new Core::Cond();
{
lock_guard<mutex> lock2(d->StackMutex);
d->suspended_tools.push(nc);
}
// wait until Core::Update() wakes up the tool
{
lock_guard<mutex> lock(d->AccessMutex);
nc->Lock(&d->AccessMutex);
assert(d->df_suspend_depth == 0);
d->df_suspend_thread = tid;
d->df_suspend_depth = 1;
}
}
void Core::Resume()
{
auto tid = this_thread::get_id();
lock_guard<mutex> lock(d->AccessMutex);
assert(d->df_suspend_depth > 0 && d->df_suspend_thread == tid);
(void)tid;
if (--d->df_suspend_depth == 0)
d->core_cond.Unlock();
} }
int Core::TileUpdate() int Core::TileUpdate()
@ -1973,40 +1928,6 @@ int Core::TileUpdate()
return true; return true;
} }
int Core::ClaimSuspend(bool force_base)
{
auto tid = this_thread::get_id();
lock_guard<mutex> lock(d->AccessMutex);
if (force_base || d->df_suspend_depth <= 0)
{
assert(d->df_suspend_depth == 0);
d->df_suspend_thread = tid;
d->df_suspend_depth = 1000000;
return 1000000;
}
else
{
assert(d->df_suspend_thread == tid);
return ++d->df_suspend_depth;
}
}
void Core::DisclaimSuspend(int level)
{
auto tid = this_thread::get_id();
lock_guard<mutex> lock(d->AccessMutex);
assert(d->df_suspend_depth == level && d->df_suspend_thread == tid);
(void)tid;
if (level == 1000000)
d->df_suspend_depth = 0;
else
--d->df_suspend_depth;
}
void Core::doUpdate(color_ostream &out, bool first_update) void Core::doUpdate(color_ostream &out, bool first_update)
{ {
Lua::Core::Reset(out, "DF code execution"); Lua::Core::Reset(out, "DF code execution");
@ -2109,8 +2030,6 @@ int Core::Update()
// Pretend this thread has suspended the core in the usual way, // Pretend this thread has suspended the core in the usual way,
// and run various processing hooks. // and run various processing hooks.
{ {
CoreSuspendClaimer suspend(true);
// Initialize the core // Initialize the core
bool first_update = false; bool first_update = false;
@ -2126,27 +2045,9 @@ int Core::Update()
doUpdate(out, first_update); doUpdate(out, first_update);
} }
// wake waiting tools // Let all commands run that require CoreSuspender
// do not allow more tools to join in while we process stuff here CoreWakeup.wait(MainThread::suspend(),
lock_guard<mutex> lock_stack(d->StackMutex); [this]() -> bool {return this->toolCount.load() == 0;});
while (!d->suspended_tools.empty())
{
Core::Cond * nc = d->suspended_tools.top();
d->suspended_tools.pop();
lock_guard<mutex> lock(d->AccessMutex);
// wake tool
nc->Unlock();
// wait for tool to wake us
d->core_cond.Lock(&d->AccessMutex);
// verify
assert(d->df_suspend_depth == 0);
// destroy condition
delete nc;
// check lua stack depth
Lua::Core::Reset(out, "suspend");
}
return 0; return 0;
}; };
@ -2360,12 +2261,31 @@ void Core::onStateChange(color_ostream &out, state_change_event event)
handleLoadAndUnloadScripts(out, event); handleLoadAndUnloadScripts(out, event);
} }
// FIXME: needs to terminate the IO threads and properly dismantle all the machinery involved.
int Core::Shutdown ( void ) int Core::Shutdown ( void )
{ {
if(errorstate) if(errorstate)
return true; return true;
errorstate = 1; errorstate = 1;
// Make sure we release main thread if this is called from main thread
if (MainThread::suspend().owns_lock())
MainThread::suspend().unlock();
// Make sure the console thread shutdowns before clean up to avoid any
// unlikely data races.
if (d->iothread.joinable()) {
con.shutdown();
}
if (d->hotkeythread.joinable()) {
std::unique_lock<std::mutex> hot_lock(HotkeyMutex);
hotkey_set = SHUTDOWN;
HotkeyCond.notify_one();
}
d->hotkeythread.join();
d->iothread.join();
CoreSuspendClaimer suspend; CoreSuspendClaimer suspend;
if(plug_mgr) if(plug_mgr)
{ {
@ -2379,7 +2299,6 @@ int Core::Shutdown ( void )
} }
allModules.clear(); allModules.clear();
memset(&(s_mods), 0, sizeof(s_mods)); memset(&(s_mods), 0, sizeof(s_mods));
con.shutdown();
return -1; return -1;
} }
@ -2530,7 +2449,7 @@ bool Core::SelectHotkey(int sym, int modifiers)
std::string cmd; std::string cmd;
{ {
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex); std::lock_guard<std::mutex> lock(HotkeyMutex);
// Check the internal keybindings // Check the internal keybindings
std::vector<KeyBinding> &bindings = key_bindings[sym]; std::vector<KeyBinding> &bindings = key_bindings[sym];
@ -2629,7 +2548,7 @@ bool Core::ClearKeyBindings(std::string keyspec)
if (!parseKeySpec(keyspec, &sym, &mod, &focus)) if (!parseKeySpec(keyspec, &sym, &mod, &focus))
return false; return false;
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex); std::lock_guard<std::mutex> lock(HotkeyMutex);
std::vector<KeyBinding> &bindings = key_bindings[sym]; std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) { for (int i = bindings.size()-1; i >= 0; --i) {
@ -2668,7 +2587,7 @@ bool Core::AddKeyBinding(std::string keyspec, std::string cmdline)
if (binding.command.empty()) if (binding.command.empty())
return false; return false;
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex); std::lock_guard<std::mutex> lock(HotkeyMutex);
// Don't add duplicates // Don't add duplicates
std::vector<KeyBinding> &bindings = key_bindings[sym]; std::vector<KeyBinding> &bindings = key_bindings[sym];
@ -2692,7 +2611,7 @@ std::vector<std::string> Core::ListKeyBindings(std::string keyspec)
if (!parseKeySpec(keyspec, &sym, &mod, &focus)) if (!parseKeySpec(keyspec, &sym, &mod, &focus))
return rv; return rv;
tthread::lock_guard<tthread::mutex> lock(*HotkeyMutex); std::lock_guard<std::mutex> lock(HotkeyMutex);
std::vector<KeyBinding> &bindings = key_bindings[sym]; std::vector<KeyBinding> &bindings = key_bindings[sym];
for (int i = bindings.size()-1; i >= 0; --i) { for (int i = bindings.size()-1; i >= 0; --i) {
@ -2712,7 +2631,7 @@ std::vector<std::string> Core::ListKeyBindings(std::string keyspec)
bool Core::AddAlias(const std::string &name, const std::vector<std::string> &command, bool replace) bool Core::AddAlias(const std::string &name, const std::vector<std::string> &command, bool replace)
{ {
tthread::lock_guard<tthread::recursive_mutex> lock(*alias_mutex); std::lock_guard<std::recursive_mutex> lock(alias_mutex);
if (!IsAlias(name) || replace) if (!IsAlias(name) || replace)
{ {
aliases[name] = command; aliases[name] = command;
@ -2723,7 +2642,7 @@ bool Core::AddAlias(const std::string &name, const std::vector<std::string> &com
bool Core::RemoveAlias(const std::string &name) bool Core::RemoveAlias(const std::string &name)
{ {
tthread::lock_guard<tthread::recursive_mutex> lock(*alias_mutex); std::lock_guard<std::recursive_mutex> lock(alias_mutex);
if (IsAlias(name)) if (IsAlias(name))
{ {
aliases.erase(name); aliases.erase(name);
@ -2734,14 +2653,14 @@ bool Core::RemoveAlias(const std::string &name)
bool Core::IsAlias(const std::string &name) bool Core::IsAlias(const std::string &name)
{ {
tthread::lock_guard<tthread::recursive_mutex> lock(*alias_mutex); std::lock_guard<std::recursive_mutex> lock(alias_mutex);
return aliases.find(name) != aliases.end(); return aliases.find(name) != aliases.end();
} }
bool Core::RunAlias(color_ostream &out, const std::string &name, bool Core::RunAlias(color_ostream &out, const std::string &name,
const std::vector<std::string> &parameters, command_result &result) const std::vector<std::string> &parameters, command_result &result)
{ {
tthread::lock_guard<tthread::recursive_mutex> lock(*alias_mutex); std::lock_guard<std::recursive_mutex> lock(alias_mutex);
if (!IsAlias(name)) if (!IsAlias(name))
{ {
return false; return false;
@ -2756,13 +2675,13 @@ bool Core::RunAlias(color_ostream &out, const std::string &name,
std::map<std::string, std::vector<std::string>> Core::ListAliases() std::map<std::string, std::vector<std::string>> Core::ListAliases()
{ {
tthread::lock_guard<tthread::recursive_mutex> lock(*alias_mutex); std::lock_guard<std::recursive_mutex> lock(alias_mutex);
return aliases; return aliases;
} }
std::string Core::GetAliasCommand(const std::string &name, const std::string &default_) std::string Core::GetAliasCommand(const std::string &name, const std::string &default_)
{ {
tthread::lock_guard<tthread::recursive_mutex> lock(*alias_mutex); std::lock_guard<std::recursive_mutex> lock(alias_mutex);
if (IsAlias(name)) if (IsAlias(name))
return join_strings(" ", aliases[name]); return join_strings(" ", aliases[name]);
else else

@ -463,7 +463,7 @@ DFhackCExport char * SDL_GetError(void)
} }
static void (*_SDL_SetError)(const char *fmt, ...) = 0; static void (*_SDL_SetError)(const char *fmt, ...) = 0;
DFhackCExport void SDL_SetError(const char *fmt, ...) DFhackCExport void SDL_SetError(const char *fmt, ...) Wformat(printf,1,2)
{ {
char buf[1024]; char buf[1024];
va_list args; va_list args;

@ -1626,6 +1626,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isMale), WRAPM(Units, isMale),
WRAPM(Units, isFemale), WRAPM(Units, isFemale),
WRAPM(Units, isMerchant), WRAPM(Units, isMerchant),
WRAPM(Units, isDiplomat),
WRAPM(Units, isForest), WRAPM(Units, isForest),
WRAPM(Units, isMarkedForSlaughter), WRAPM(Units, isMarkedForSlaughter),
WRAPM(Units, isTame), WRAPM(Units, isTame),
@ -2242,11 +2243,7 @@ int screen_show(lua_State *L)
df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, true); df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, true);
bool ok = Screen::show(screen, before); bool ok = Screen::show(std::unique_ptr<df::viewscreen>{screen}, before);
// If it is a table, get_pointer created a new object. Don't leak it.
if (!ok && lua_istable(L, 1))
delete screen;
lua_pushboolean(L, ok); lua_pushboolean(L, ok);
return 1; return 1;

@ -283,9 +283,6 @@ static int lua_dfhack_is_interactive(lua_State *S)
static int dfhack_lineedit_sync(lua_State *S, Console *pstream) static int dfhack_lineedit_sync(lua_State *S, Console *pstream)
{ {
if (!pstream)
return 2;
const char *prompt = luaL_optstring(S, 1, ">> "); const char *prompt = luaL_optstring(S, 1, ">> ");
const char *hfile = luaL_optstring(S, 2, NULL); const char *hfile = luaL_optstring(S, 2, NULL);
@ -296,10 +293,16 @@ static int dfhack_lineedit_sync(lua_State *S, Console *pstream)
std::string ret; std::string ret;
int rv = pstream->lineedit(prompt, ret, hist); int rv = pstream->lineedit(prompt, ret, hist);
if (rv == Console::RETRY)
rv = 0; /* return empty string to lua */
if (rv < 0) if (rv < 0)
{ {
lua_pushnil(S); lua_pushnil(S);
lua_pushstring(S, "input error"); if (rv == Console::SHUTDOWN)
lua_pushstring(S, "shutdown requested");
else
lua_pushstring(S, "input error");
return 2; return 2;
} }
else else
@ -333,8 +336,11 @@ static int dfhack_lineedit(lua_State *S)
lua_settop(S, 2); lua_settop(S, 2);
Console *pstream = get_console(S); Console *pstream = get_console(S);
if (!pstream) if (!pstream) {
lua_pushnil(S);
lua_pushstring(S, "no console");
return 2; return 2;
}
lua_rawgetp(S, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); lua_rawgetp(S, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN);
lua_rawgetp(S, -1, S); lua_rawgetp(S, -1, S);
@ -1058,7 +1064,11 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
prompt = ">> "; prompt = ">> ";
std::string curline; std::string curline;
con.lineedit(prompt,curline,hist); while((rv = con.lineedit(prompt,curline,hist)) == Console::RETRY);
if (rv <= Console::FAILURE) {
rv = rv == Console::SHUTDOWN ? LUA_OK : LUA_ERRRUN;
break;
}
hist.add(curline); hist.add(curline);
{ {

@ -27,6 +27,7 @@ distribution.
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
#include <cinttypes>
#include "MemAccess.h" #include "MemAccess.h"
#include "Core.h" #include "Core.h"
@ -1008,7 +1009,7 @@ static int meta_ptr_tostring(lua_State *state)
const char *cname = lua_tostring(state, -1); const char *cname = lua_tostring(state, -1);
if (has_length) if (has_length)
lua_pushstring(state, stl_sprintf("<%s[%llu]: %p>", cname, length, (void*)ptr).c_str()); lua_pushstring(state, stl_sprintf("<%s[%" PRIu64 "]: %p>", cname, length, (void*)ptr).c_str());
else else
lua_pushstring(state, stl_sprintf("<%s: %p>", cname, (void*)ptr).c_str()); lua_pushstring(state, stl_sprintf("<%s: %p>", cname, (void*)ptr).c_str());
return 1; return 1;

@ -27,6 +27,8 @@ distribution.
#include "MiscUtils.h" #include "MiscUtils.h"
#ifndef LINUX_BUILD #ifndef LINUX_BUILD
// We don't want min and max macros
#define NOMINMAX
#include <Windows.h> #include <Windows.h>
#else #else
#include <sys/time.h> #include <sys/time.h>
@ -40,6 +42,7 @@ distribution.
#include <sstream> #include <sstream>
#include <map> #include <map>
#include <array>
std::string stl_sprintf(const char *fmt, ...) { std::string stl_sprintf(const char *fmt, ...) {
va_list lst; va_list lst;
@ -50,21 +53,25 @@ std::string stl_sprintf(const char *fmt, ...) {
} }
std::string stl_vsprintf(const char *fmt, va_list args) { std::string stl_vsprintf(const char *fmt, va_list args) {
std::vector<char> buf; /* Allow small (about single line) strings to be printed into stack memory
buf.resize(4096); * with a call to vsnprintf.
for (;;) { */
va_list args2; std::array<char,128> buf;
va_copy(args2, args); va_list args2;
int rsz = vsnprintf(&buf[0], buf.size(), fmt, args2); va_copy(args2, args);
va_end(args2); int rsz = vsnprintf(&buf[0], buf.size(), fmt, args2);
va_end(args2);
if (rsz < 0) if (rsz < 0)
buf.resize(buf.size()*2); return std::string(); /* Error occurred */
else if (unsigned(rsz) >= buf.size()) if (static_cast<unsigned>(rsz) < buf.size())
buf.resize(rsz+1); return std::string(&buf[0], rsz); /* Whole string fits to a single line buffer */
else std::string rv;
return std::string(&buf[0], rsz); // Allocate enough memory for the output and null termination
} rv.resize(rsz);
rsz = vsnprintf(&rv[0], rv.size()+1, fmt, args);
if (rsz < static_cast<int>(rv.size()))
rv.resize(std::max(rsz,0));
return rv;
} }
bool split_string(std::vector<std::string> *out, bool split_string(std::vector<std::string> *out,

@ -49,7 +49,6 @@ using namespace DFHack;
using namespace std; using namespace std;
#include "tinythread.h" #include "tinythread.h"
using namespace tthread;
#include <assert.h> #include <assert.h>
@ -83,8 +82,8 @@ struct Plugin::RefLock
RefLock() RefLock()
{ {
refcount = 0; refcount = 0;
wakeup = new condition_variable(); wakeup = new tthread::condition_variable();
mut = new mutex(); mut = new tthread::mutex();
} }
~RefLock() ~RefLock()
{ {
@ -119,8 +118,8 @@ struct Plugin::RefLock
wakeup->wait(*mut); wakeup->wait(*mut);
} }
} }
condition_variable * wakeup; tthread::condition_variable * wakeup;
mutex * mut; tthread::mutex * mut;
int refcount; int refcount;
}; };
@ -786,8 +785,8 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
PluginManager::PluginManager(Core * core) : core(core) PluginManager::PluginManager(Core * core) : core(core)
{ {
plugin_mutex = new recursive_mutex(); plugin_mutex = new tthread::recursive_mutex();
cmdlist_mutex = new mutex(); cmdlist_mutex = new tthread::mutex();
ruby = NULL; ruby = NULL;
} }

@ -55,9 +55,10 @@ POSSIBILITY OF SUCH DAMAGE.
#include <memory> #include <memory>
using namespace DFHack; #include "json/json.h"
#include "tinythread.h" #include "tinythread.h"
using namespace DFHack;
using namespace tthread; using namespace tthread;
using dfproto::CoreTextNotification; using dfproto::CoreTextNotification;
@ -132,14 +133,34 @@ bool readFullBuffer(CSimpleSocket *socket, void *buf, int size)
int RemoteClient::GetDefaultPort() int RemoteClient::GetDefaultPort()
{ {
const char *port = getenv("DFHACK_PORT"); int port = DEFAULT_PORT;
if (!port) port = "0";
int portval = atoi(port); const char *port_env = getenv("DFHACK_PORT");
if (portval <= 0) if (port_env)
return 5000; {
int port_val = atoi(port_env);
if (port_val > 0)
port = port_val;
}
else else
return portval; {
for (const char *filename : {"dfhack-config/remote-server.json", "../dfhack-config/remote-server.json"})
{
std::ifstream in_file(filename, std::ios_base::in);
if (in_file)
{
Json::Value config;
in_file >> config;
in_file.close();
if (config.isMember("port")) {
port = config["port"].asInt();
break;
}
}
}
}
return port;
} }
bool RemoteClient::connect(int port) bool RemoteClient::connect(int port)
@ -157,7 +178,7 @@ bool RemoteClient::connect(int port)
if (!socket->Open("localhost", port)) if (!socket->Open("localhost", port))
{ {
default_output().printerr("Could not connect to localhost: %d\n", port); default_output().printerr("Could not connect to localhost:%d\n", port);
return false; return false;
} }

@ -58,12 +58,11 @@ POSSIBILITY OF SUCH DAMAGE.
#include <memory> #include <memory>
using namespace DFHack; #include "json/json.h"
#include "tinythread.h" #include "tinythread.h"
using namespace tthread;
#include "jsoncpp.h" using namespace DFHack;
using namespace tthread;
using dfproto::CoreTextNotification; using dfproto::CoreTextNotification;
using dfproto::CoreTextFragment; using dfproto::CoreTextFragment;
@ -395,11 +394,11 @@ bool ServerMain::listen(int port)
inFile.close(); inFile.close();
allow_remote = configJson.get("allow_remote", "false").asBool(); allow_remote = configJson.get("allow_remote", "false").asBool();
port = configJson.get("port", port).asInt();
} }
// rewrite/normalize config file
configJson["allow_remote"] = allow_remote; configJson["allow_remote"] = allow_remote;
configJson["port"] = port; configJson["port"] = configJson.get("port", RemoteClient::DEFAULT_PORT);
std::ofstream outFile(filename, std::ios_base::trunc); std::ofstream outFile(filename, std::ios_base::trunc);
@ -409,6 +408,7 @@ bool ServerMain::listen(int port)
outFile.close(); outFile.close();
} }
std::cerr << "Listening on port " << port << (allow_remote ? " (remote enabled)" : "") << std::endl;
if (allow_remote) if (allow_remote)
{ {
if (!socket->Listen(NULL, port)) if (!socket->Listen(NULL, port))

@ -652,8 +652,10 @@ static command_result SetUnitLabors(color_ostream &stream, const SetUnitLaborsIn
return CR_OK; return CR_OK;
} }
CoreService::CoreService() { CoreService::CoreService() :
suspend_depth = 0; suspend_depth{0},
coreSuspender{nullptr}
{
// These 2 methods must be first, so that they get id 0 and 1 // These 2 methods must be first, so that they get id 0 and 1
addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND | SF_ALLOW_REMOTE); addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
@ -683,8 +685,7 @@ CoreService::CoreService() {
CoreService::~CoreService() CoreService::~CoreService()
{ {
while (suspend_depth-- > 0) delete coreSuspender;
Core::getInstance().Resume();
} }
command_result CoreService::BindMethod(color_ostream &stream, command_result CoreService::BindMethod(color_ostream &stream,
@ -725,7 +726,8 @@ command_result CoreService::RunCommand(color_ostream &stream,
command_result CoreService::CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt) command_result CoreService::CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt)
{ {
Core::getInstance().Suspend(); if (suspend_depth == 0)
coreSuspender = new CoreSuspender();
cnt->set_value(++suspend_depth); cnt->set_value(++suspend_depth);
return CR_OK; return CR_OK;
} }
@ -735,8 +737,11 @@ command_result CoreService::CoreResume(color_ostream &stream, const EmptyMessage
if (suspend_depth <= 0) if (suspend_depth <= 0)
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
Core::getInstance().Resume();
cnt->set_value(--suspend_depth); cnt->set_value(--suspend_depth);
if (suspend_depth == 0) {
delete coreSuspender;
coreSuspender = nullptr;
}
return CR_OK; return CR_OK;
} }

@ -25,12 +25,14 @@ distribution.
#pragma once #pragma once
#include "Pragma.h" #include "Pragma.h"
#include "Export.h" #include "Export.h"
#include "Error.h"
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <sstream> #include <sstream>
#include <exception> #include <exception>
//#include <ostream> #include <type_traits>
#include <iterator>
namespace DFHack namespace DFHack
{ {
template <typename T = int> template <typename T = int>
@ -232,4 +234,338 @@ namespace DFHack
m_data[idx] = item; m_data[idx] = item;
} }
}; };
template <typename L, typename I>
struct DfLinkedList
{
class iterator;
class const_iterator;
class proxy
{
L *cur;
friend struct DfLinkedList<L, I>;
friend class iterator;
proxy(L *cur) : cur(cur)
{
CHECK_NULL_POINTER(cur);
}
public:
operator I *const &() const
{
return cur->item;
}
I *operator->() const
{
return cur->item;
}
proxy & operator=(I *const & item)
{
if (item)
{
CHECK_INVALID_ARGUMENT(item->dfhack_get_list_link() == nullptr);
item->dfhack_set_list_link(cur);
}
if (cur->item)
{
cur->item->dfhack_set_list_link(nullptr);
}
cur->item = item;
return *this;
}
};
class iterator
{
L *root;
L *cur;
friend struct DfLinkedList<L, I>;
friend class const_iterator;
iterator(L *root, L *cur) : root(root), cur(cur) {}
public:
using difference_type = void;
using value_type = I *;
using pointer = I **;
using reference = proxy;
using iterator_category = std::bidirectional_iterator_tag;
iterator() : root(nullptr), cur(nullptr) {}
iterator(const iterator & other) : root(other.root), cur(other.cur) {}
iterator & operator++()
{
CHECK_NULL_POINTER(root);
CHECK_NULL_POINTER(cur);
cur = cur->next;
return *this;
}
iterator & operator--()
{
CHECK_NULL_POINTER(root);
if (!cur)
{
// find end() - 1
for (cur = root->next; cur && cur->next; cur = cur->next)
{
}
return *this;
}
CHECK_NULL_POINTER(cur);
CHECK_NULL_POINTER(cur->prev);
cur = cur->prev;
return *this;
}
iterator operator++(int)
{
iterator copy(*this);
++*this;
return copy;
}
iterator operator--(int)
{
iterator copy(*this);
--*this;
return copy;
}
iterator & operator=(const iterator & other)
{
root = other.root;
cur = other.cur;
return *this;
}
proxy operator*()
{
CHECK_NULL_POINTER(root);
CHECK_NULL_POINTER(cur);
return proxy(cur);
}
I *const & operator*() const
{
CHECK_NULL_POINTER(root);
CHECK_NULL_POINTER(cur);
return cur->item;
}
operator const_iterator() const
{
return const_iterator(*this);
}
bool operator==(const iterator & other) const
{
return root == other.root && cur == other.cur;
}
bool operator!=(const iterator & other) const
{
return !(*this == other);
}
};
class const_iterator
{
iterator iter;
friend struct DfLinkedList<L, I>;
public:
using difference_type = void;
using value_type = I *;
using pointer = I *const *;
using reference = I *const &;
using iterator_category = std::bidirectional_iterator_tag;
const_iterator(const iterator & iter) : iter(iter) {}
const_iterator(const const_iterator & other) : iter(other.iter) {}
const_iterator & operator++()
{
++iter;
return *this;
}
const_iterator & operator--()
{
--iter;
return *this;
}
const_iterator operator++(int)
{
const_iterator copy(*this);
++iter;
return copy;
}
const_iterator operator--(int)
{
const_iterator copy(*this);
--iter;
return copy;
}
const_iterator & operator=(const const_iterator & other)
{
iter = other.iter;
return *this;
}
I *const & operator*() const
{
return *iter;
}
bool operator==(const const_iterator & other) const
{
return iter == other.iter;
}
bool operator!=(const const_iterator & other) const
{
return iter != other.iter;
}
};
using value_type = I *;
using reference_type = proxy;
using difference_type = void;
using size_type = size_t;
bool empty() const
{
return static_cast<const L *>(this)->next == nullptr;
}
size_t size() const
{
size_t n = 0;
for (value_type const & i : *this)
n++;
return n;
}
iterator begin()
{
return iterator(static_cast<L *>(this), static_cast<L *>(this)->next);
}
const_iterator begin() const
{
return const_iterator(const_cast<DfLinkedList<L, I> *>(this)->begin());
}
const_iterator cbegin() const
{
return begin();
}
iterator end()
{
return iterator(static_cast<L *>(this), nullptr);
}
const_iterator end() const
{
return const_iterator(const_cast<DfLinkedList<L, I> *>(this)->end());
}
const_iterator cend() const
{
return end();
}
iterator erase(const_iterator pos)
{
auto root = static_cast<L *>(this);
CHECK_INVALID_ARGUMENT(pos.iter.root == root);
CHECK_NULL_POINTER(pos.iter.cur);
auto link = pos.iter.cur;
auto next = link->next;
if (link->prev)
{
link->prev->next = link->next;
}
else
{
root->next = link->next;
}
if (link->next)
{
link->next->prev = link->prev;
}
proxy p(link);
p = nullptr;
delete link;
return iterator(root, next);
}
iterator insert(const_iterator pos, I *const & item)
{
auto root = static_cast<L *>(this);
CHECK_INVALID_ARGUMENT(pos.iter.root == root);
auto link = pos.iter.cur;
if (!link || !link->prev)
{
if (!link && root->next)
{
pos--;
return insert_after(pos, item);
}
CHECK_INVALID_ARGUMENT(root->next == link);
push_front(item);
return begin();
}
auto newlink = new L();
newlink->prev = link->prev;
newlink->next = link;
link->prev = newlink;
if (newlink->prev)
{
newlink->prev->next = newlink;
}
else if (link == root->next)
{
root->next = newlink;
}
newlink->item = nullptr;
proxy p(newlink);
p = item;
return iterator(root, newlink);
}
iterator insert_after(const_iterator pos, I *const & item)
{
auto root = static_cast<L *>(this);
CHECK_INVALID_ARGUMENT(pos.iter.root == root);
CHECK_NULL_POINTER(pos.iter.cur);
auto link = pos.iter.cur;
auto next = link->next;
auto newlink = new L();
newlink->prev = link;
newlink->next = next;
link->next = newlink;
if (next)
{
next->prev = newlink;
}
newlink->item = nullptr;
proxy p(newlink);
p= item;
return iterator(root, newlink);
}
void push_front(I *const & item)
{
auto root = static_cast<L *>(this);
auto link = new L();
link->prev = nullptr;
if (root->next)
{
root->next->prev = link;
link->next = root->next;
}
link->item = nullptr;
proxy p(link);
p = item;
root->next = link;
}
};
} }

@ -106,12 +106,12 @@ namespace DFHack
virtual ~color_ostream(); virtual ~color_ostream();
/// Print a formatted string, like printf /// Print a formatted string, like printf
void print(const char *format, ...); void print(const char *format, ...) Wformat(printf,2,3);
void vprint(const char *format, va_list args); void vprint(const char *format, va_list args) Wformat(printf,2,0);
/// Print a formatted string, like printf, in red /// Print a formatted string, like printf, in red
void printerr(const char *format, ...); void printerr(const char *format, ...) Wformat(printf,2,3);
void vprinterr(const char *format, va_list args); void vprinterr(const char *format, va_list args) Wformat(printf,2,0);
/// Get color /// Get color
color_value color() { return cur_color; } color_value color() { return cur_color; }

@ -26,6 +26,7 @@ distribution.
#include "Pragma.h" #include "Pragma.h"
#include "Export.h" #include "Export.h"
#include "ColorText.h" #include "ColorText.h"
#include <atomic>
#include <deque> #include <deque>
#include <fstream> #include <fstream>
#include <assert.h> #include <assert.h>
@ -152,6 +153,12 @@ namespace DFHack
int get_rows(void); int get_rows(void);
/// beep. maybe? /// beep. maybe?
//void beep (void); //void beep (void);
//! \defgroup lineedit_return_values Possible errors from lineedit
//! \{
static constexpr int FAILURE = -1;
static constexpr int SHUTDOWN = -2;
static constexpr int RETRY = -3;
//! \}
/// A simple line edit (raw mode) /// A simple line edit (raw mode)
int lineedit(const std::string& prompt, std::string& output, CommandHistory & history ); int lineedit(const std::string& prompt, std::string& output, CommandHistory & history );
bool isInited (void) { return inited; }; bool isInited (void) { return inited; };
@ -163,6 +170,6 @@ namespace DFHack
private: private:
Private * d; Private * d;
tthread::recursive_mutex * wlock; tthread::recursive_mutex * wlock;
bool inited; std::atomic<bool> inited;
}; };
} }

@ -34,6 +34,11 @@ distribution.
#include "Console.h" #include "Console.h"
#include "modules/Graphic.h" #include "modules/Graphic.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "RemoteClient.h" #include "RemoteClient.h"
#define DFH_MOD_SHIFT 1 #define DFH_MOD_SHIFT 1
@ -42,13 +47,6 @@ distribution.
struct WINDOW; struct WINDOW;
namespace tthread
{
class mutex;
class condition_variable;
class thread;
}
namespace df namespace df
{ {
struct viewscreen; struct viewscreen;
@ -65,11 +63,21 @@ namespace DFHack
class PluginManager; class PluginManager;
class Core; class Core;
class ServerMain; class ServerMain;
class CoreSuspender;
namespace Lua { namespace Core {
DFHACK_EXPORT void Reset(color_ostream &out, const char *where);
} }
namespace Windows namespace Windows
{ {
class df_window; class df_window;
} }
namespace Screen
{
struct Hide;
}
enum state_change_event enum state_change_event
{ {
SC_UNKNOWN = -1, SC_UNKNOWN = -1,
@ -133,10 +141,6 @@ namespace DFHack
} }
/// check if the activity lock is owned by this thread /// check if the activity lock is owned by this thread
bool isSuspended(void); bool isSuspended(void);
/// try to acquire the activity lock
void Suspend(void);
/// return activity lock
void Resume(void);
/// Is everything OK? /// Is everything OK?
bool isValid(void) { return !errorstate; } bool isValid(void) { return !errorstate; }
@ -149,7 +153,7 @@ namespace DFHack
/// sets the current hotkey command /// sets the current hotkey command
bool setHotkeyCmd( std::string cmd ); bool setHotkeyCmd( std::string cmd );
/// removes the hotkey command and gives it to the caller thread /// removes the hotkey command and gives it to the caller thread
std::string getHotkeyCmd( void ); std::string getHotkeyCmd( bool &keep_going );
/// adds a named pointer (for later or between plugins) /// adds a named pointer (for later or between plugins)
void RegisterData(void *p,std::string key); void RegisterData(void *p,std::string key);
@ -191,8 +195,8 @@ namespace DFHack
DFHack::VersionInfo * vinfo; DFHack::VersionInfo * vinfo;
DFHack::Windows::df_window * screen_window; DFHack::Windows::df_window * screen_window;
static void print(const char *format, ...); static void print(const char *format, ...) Wformat(printf,1,2);
static void printerr(const char *format, ...); static void printerr(const char *format, ...) Wformat(printf,1,2);
PluginManager *getPluginManager() { return plug_mgr; } PluginManager *getPluginManager() { return plug_mgr; }
@ -202,14 +206,11 @@ namespace DFHack
DFHack::Console con; DFHack::Console con;
Core(); Core();
~Core();
struct Private; struct Private;
Private *d; Private *d;
friend class CoreSuspendClaimer;
int ClaimSuspend(bool force_base);
void DisclaimSuspend(int level);
bool Init(); bool Init();
int Update (void); int Update (void);
int TileUpdate (void); int TileUpdate (void);
@ -246,7 +247,7 @@ namespace DFHack
DFHack::PluginManager * plug_mgr; DFHack::PluginManager * plug_mgr;
std::vector<std::string> script_paths[2]; std::vector<std::string> script_paths[2];
tthread::mutex *script_path_mutex; std::mutex script_path_mutex;
// hotkey-related stuff // hotkey-related stuff
struct KeyBinding { struct KeyBinding {
@ -260,12 +261,17 @@ namespace DFHack
std::map<int, std::vector<KeyBinding> > key_bindings; std::map<int, std::vector<KeyBinding> > key_bindings;
std::map<int, bool> hotkey_states; std::map<int, bool> hotkey_states;
std::string hotkey_cmd; std::string hotkey_cmd;
bool hotkey_set; enum hotkey_set_t {
tthread::mutex * HotkeyMutex; NO,
tthread::condition_variable * HotkeyCond; SET,
SHUTDOWN,
};
hotkey_set_t hotkey_set;
std::mutex HotkeyMutex;
std::condition_variable HotkeyCond;
std::map<std::string, std::vector<std::string>> aliases; std::map<std::string, std::vector<std::string>> aliases;
tthread::recursive_mutex * alias_mutex; std::recursive_mutex alias_mutex;
bool SelectHotkey(int key, int modifiers); bool SelectHotkey(int key, int modifiers);
@ -273,6 +279,7 @@ namespace DFHack
void *last_world_data_ptr; void *last_world_data_ptr;
// for state change tracking // for state change tracking
void *last_local_map_ptr; void *last_local_map_ptr;
friend struct Screen::Hide;
df::viewscreen *top_viewscreen; df::viewscreen *top_viewscreen;
bool last_pause_state; bool last_pause_state;
// Very important! // Very important!
@ -280,35 +287,158 @@ namespace DFHack
// Additional state change scripts // Additional state change scripts
std::vector<StateChangeScript> state_change_scripts; std::vector<StateChangeScript> state_change_scripts;
tthread::mutex * misc_data_mutex; std::mutex misc_data_mutex;
std::map<std::string,void*> misc_data_map; std::map<std::string,void*> misc_data_map;
/*!
* \defgroup core_suspend CoreSuspender state handling serialization to
* DF memory.
* \sa DFHack::CoreSuspender
* \{
*/
std::recursive_mutex CoreSuspendMutex;
std::condition_variable_any CoreWakeup;
std::atomic<std::thread::id> ownerThread;
std::atomic<size_t> toolCount;
//! \}
friend class CoreService; friend class CoreService;
friend class ServerConnection; friend class ServerConnection;
friend class CoreSuspender;
friend class CoreSuspenderBase;
friend struct CoreSuspendClaimMain;
friend struct CoreSuspendReleaseMain;
ServerMain *server; ServerMain *server;
}; };
class CoreSuspender { class CoreSuspenderBase : protected std::unique_lock<std::recursive_mutex> {
Core *core; protected:
using parent_t = std::unique_lock<std::recursive_mutex>;
std::thread::id tid;
CoreSuspenderBase(std::defer_lock_t d) : CoreSuspenderBase{&Core::getInstance(), d} {}
CoreSuspenderBase(Core* core, std::defer_lock_t) :
/* Lock the core */
parent_t{core->CoreSuspendMutex,std::defer_lock},
/* Mark this thread to be the core owner */
tid{}
{}
public: public:
CoreSuspender() : core(&Core::getInstance()) { core->Suspend(); } void lock()
CoreSuspender(Core *core) : core(core) { core->Suspend(); } {
~CoreSuspender() { core->Resume(); } auto& core = Core::getInstance();
parent_t::lock();
tid = core.ownerThread.exchange(std::this_thread::get_id(),
std::memory_order_acquire);
}
void unlock()
{
auto& core = Core::getInstance();
/* Restore core owner to previous value */
core.ownerThread.store(tid, std::memory_order_release);
if (tid == std::thread::id{})
Lua::Core::Reset(core.getConsole(), "suspend");
parent_t::unlock();
}
bool owns_lock() const noexcept
{
return parent_t::owns_lock();
}
~CoreSuspenderBase() {
if (owns_lock())
unlock();
}
friend class MainThread;
}; };
/** Claims the current thread already has the suspend lock. /*!
* Strictly for use in callbacks from DF. * CoreSuspender allows serialization to DF data with std::unique_lock like
* interface. It includes handling for recursive CoreSuspender calls and
* notification to main thread after all queue tools have been handled.
*
* State transitions are:
* - Startup setups Core::SuspendMutex to unlocked states
* - Core::Init locks Core::SuspendMutex until the thread exits or that thread
* calls Core::Shutdown or Core::~Core.
* - Other thread request core suspend by atomic incrementation of Core::toolCount
* and then locking Core::CoreSuspendMutex. After locking CoreSuspendMutex
* success callers exchange their std::thread::id to Core::ownerThread.
* - Core::Update() makes sure that queued tools are run when it calls
* Core::CoreWakup::wait. The wait keeps Core::CoreSuspendMutex unlocked
* and waits until Core::toolCount is reduced back to zero.
* - CoreSuspender::~CoreSuspender() first stores the previous Core::ownerThread
* back. In case of recursive call Core::ownerThread equals tid. If tis is
* zero then we are releasing the recursive_mutex which means suspend
* context is over. It is time to reset lua.
* The last step is to decrement Core::toolCount and wakeup main thread if
* no more tools are queued trying to acquire the
* Core::CoreSuspenderMutex.
*/ */
class CoreSuspendClaimer { class CoreSuspender : public CoreSuspenderBase {
Core *core; using parent_t = CoreSuspenderBase;
int level;
public: public:
CoreSuspendClaimer(bool base = false) : core(&Core::getInstance()) { CoreSuspender() : CoreSuspender{&Core::getInstance()} { }
level = core->ClaimSuspend(base); CoreSuspender(std::defer_lock_t d) : CoreSuspender{&Core::getInstance(),d} { }
CoreSuspender(bool) : CoreSuspender{&Core::getInstance()} { }
CoreSuspender(Core* core, bool) : CoreSuspender{core} { }
CoreSuspender(Core* core) :
CoreSuspenderBase{core, std::defer_lock}
{
lock();
} }
CoreSuspendClaimer(Core *core, bool base = false) : core(core) { CoreSuspender(Core* core, std::defer_lock_t) :
level = core->ClaimSuspend(base); CoreSuspenderBase{core, std::defer_lock}
{}
void lock()
{
auto& core = Core::getInstance();
core.toolCount.fetch_add(1, std::memory_order_relaxed);
parent_t::lock();
}
void unlock()
{
auto& core = Core::getInstance();
parent_t::unlock();
/* Notify core to continue when all queued tools have completed
* 0 = None wants to own the core
* 1+ = There are tools waiting core access
* fetch_add returns old value before subtraction
*/
if (core.toolCount.fetch_add(-1, std::memory_order_relaxed) == 1)
core.CoreWakeup.notify_one();
} }
~CoreSuspendClaimer() { core->DisclaimSuspend(level); }
~CoreSuspender() {
if (owns_lock())
unlock();
}
};
/*!
* Temporary release main thread ownership to allow alternative thread
* implement DF logic thread loop
*/
struct DFHACK_EXPORT CoreSuspendReleaseMain {
CoreSuspendReleaseMain();
~CoreSuspendReleaseMain();
}; };
/*!
* Temporary claim main thread ownership. This allows caller to call
* Core::Update from a different thread than original DF logic thread if
* logic thread has released main thread ownership with
* CoreSuspendReleaseMain
*/
struct DFHACK_EXPORT CoreSuspendClaimMain {
CoreSuspendClaimMain();
~CoreSuspendClaimMain();
};
using CoreSuspendClaimer = CoreSuspender;
} }

@ -439,6 +439,7 @@ namespace df
using DFHack::enum_list_attr; using DFHack::enum_list_attr;
using DFHack::BitArray; using DFHack::BitArray;
using DFHack::DfArray; using DFHack::DfArray;
using DFHack::DfLinkedList;
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"

@ -58,3 +58,14 @@ distribution.
#define DFhackCExport extern "C" __declspec(dllexport) #define DFhackCExport extern "C" __declspec(dllexport)
#define DFhackDataExport extern "C" __declspec(dllexport) #define DFhackDataExport extern "C" __declspec(dllexport)
#endif #endif
// Make gcc warn if types and format string don't match for printf
#ifdef __GNUC__
//! Tell GCC about format functions to allow parameter strict type checks
//! \param type The type of function can be printf, scanf, strftime or strfmon
//! \param fmtstr One based position index for format parameter
//! \param vararg One based position index for the first checked parameter
#define Wformat(type, fmtstr, vararg) __attribute__ ((format (type, fmtstr, vararg)))
#else
#define Wformat(type, fmtstr, vararg)
#endif

@ -395,7 +395,7 @@ namespace DFHack {namespace Lua {
// Not exported; for use by the Core class // Not exported; for use by the Core class
bool Init(color_ostream &out); bool Init(color_ostream &out);
void Reset(color_ostream &out, const char *where); DFHACK_EXPORT void Reset(color_ostream &out, const char *where);
// Events signalled by the core // Events signalled by the core
void onStateChange(color_ostream &out, int code); void onStateChange(color_ostream &out, int code);

@ -31,6 +31,7 @@ distribution.
#include <vector> #include <vector>
#include <sstream> #include <sstream>
#include <cstdio> #include <cstdio>
#include <memory>
using std::ostream; using std::ostream;
using std::stringstream; using std::stringstream;
@ -44,6 +45,28 @@ using std::endl;
#define DFHACK_FUNCTION_SIG __func__ #define DFHACK_FUNCTION_SIG __func__
#endif #endif
/*! \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 T, typename... Args>
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T> >::type
make_unique(Args&&... args)
{
return std::unique_ptr<T>{new T{std::forward<Args>(args)...}};
}
#else /* >= c++14 */
using std::make_unique;
#endif
}
template <typename T> template <typename T>
void print_bits ( T val, ostream& out ) void print_bits ( T val, ostream& out )
{ {
@ -354,8 +377,8 @@ DFHACK_EXPORT int random_int(int max);
*/ */
DFHACK_EXPORT uint64_t GetTimeMs64(); DFHACK_EXPORT uint64_t GetTimeMs64();
DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...); DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...) Wformat(printf,1,2);
DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args); DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args) Wformat(printf,1,0);
// Conversion between CP437 and UTF-8 // Conversion between CP437 and UTF-8
DFHACK_EXPORT std::string UTF2DF(const std::string &in); DFHACK_EXPORT std::string UTF2DF(const std::string &in);

@ -233,6 +233,7 @@ namespace DFHack
RemoteClient(color_ostream *default_output = NULL); RemoteClient(color_ostream *default_output = NULL);
~RemoteClient(); ~RemoteClient();
static constexpr int DEFAULT_PORT = 5000;
static int GetDefaultPort(); static int GetDefaultPort();
color_ostream &default_output() { return *p_default_output; }; color_ostream &default_output() { return *p_default_output; };

@ -133,6 +133,7 @@ namespace DFHack
class CoreService : public RPCService { class CoreService : public RPCService {
int suspend_depth; int suspend_depth;
CoreSuspender* coreSuspender;
static int doRunLuaFunction(lua_State *L); static int doRunLuaFunction(lua_State *L);
public: public:

@ -1,4 +1,4 @@
#include "jsoncpp.h" #include "json/json.h"
#pragma once #pragma once
namespace Json { namespace Json {
@ -24,8 +24,10 @@ namespace Json {
inline std::string toSimpleString (const Json::Value &val) inline std::string toSimpleString (const Json::Value &val)
{ {
Json::FastWriter w; StreamWriterBuilder builder;
return w.write(val); builder["commentStyle"] = "None";
builder["indentation"] = "\t";
return writeString(builder, val);
} }
} }

@ -38,6 +38,8 @@ distribution.
#include "df/item.h" #include "df/item.h"
#include "df/inclusion_type.h" #include "df/inclusion_type.h"
#include <bitset>
namespace df { namespace df {
struct world_region_details; struct world_region_details;
} }
@ -127,22 +129,22 @@ public:
/// Arbitrary tag field for flood fills etc. /// Arbitrary tag field for flood fills etc.
int16_t &tag(df::coord2d p) { int16_t &tag(df::coord2d p) {
if (!tags) init_tags(); if (!tags) init_tags();
return index_tile<int16_t&>(tags, p); return index_tile(tags, p);
} }
/// Base layer tile type (i.e. layer stone, veins, feature stone) /// Base layer tile type (i.e. layer stone, veins, feature stone)
df::tiletype baseTiletypeAt(df::coord2d p) df::tiletype baseTiletypeAt(df::coord2d p)
{ {
if (!tiles) init_tiles(); if (!tiles) init_tiles();
return index_tile<df::tiletype>(tiles->base_tiles,p); return index_tile(tiles->base_tiles,p);
} }
/// Base layer material (i.e. layer stone, veins, feature stone) /// Base layer material (i.e. layer stone, veins, feature stone)
t_matpair baseMaterialAt(df::coord2d p) t_matpair baseMaterialAt(df::coord2d p)
{ {
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
return t_matpair( return t_matpair(
index_tile<int16_t>(basemats->mat_type,p), index_tile(basemats->mat_type,p),
index_tile<int16_t>(basemats->mat_index,p) index_tile(basemats->mat_index,p)
); );
} }
/// Check if the base layer tile is a vein /// Check if the base layer tile is a vein
@ -164,13 +166,13 @@ public:
int16_t veinMaterialAt(df::coord2d p) int16_t veinMaterialAt(df::coord2d p)
{ {
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
return index_tile<int16_t>(basemats->veinmat,p); return index_tile(basemats->veinmat,p);
} }
/// Vein type at pos (even if there is no vein tile) /// Vein type at pos (even if there is no vein tile)
df::inclusion_type veinTypeAt(df::coord2d p) df::inclusion_type veinTypeAt(df::coord2d p)
{ {
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
return (df::inclusion_type)index_tile<uint8_t>(basemats->veintype,p); return (df::inclusion_type)index_tile(basemats->veintype,p);
} }
/** Sets the vein material at the specified tile position. /** Sets the vein material at the specified tile position.
@ -207,7 +209,7 @@ public:
{ {
if (!tiles) init_tiles(); if (!tiles) init_tiles();
if (tiles->con_info) if (tiles->con_info)
return index_tile<df::tiletype>(tiles->con_info->tiles,p); return index_tile(tiles->con_info->tiles,p);
return baseTiletypeAt(p); return baseTiletypeAt(p);
} }
/// Static layer material (i.e. base + constructions) /// Static layer material (i.e. base + constructions)
@ -216,8 +218,8 @@ public:
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
if (tiles->con_info) if (tiles->con_info)
return t_matpair( return t_matpair(
index_tile<int16_t>(tiles->con_info->mat_type,p), index_tile(tiles->con_info->mat_type,p),
index_tile<int16_t>(tiles->con_info->mat_index,p) index_tile(tiles->con_info->mat_index,p)
); );
return baseMaterialAt(p); return baseMaterialAt(p);
} }
@ -232,47 +234,50 @@ public:
{ {
if (!block) return tiletype::Void; if (!block) return tiletype::Void;
if (tiles) if (tiles)
return index_tile<df::tiletype>(tiles->raw_tiles,p); return index_tile(tiles->raw_tiles,p);
return index_tile<df::tiletype>(block->tiletype,p); return index_tile(block->tiletype,p);
} }
bool setTiletypeAt(df::coord2d, df::tiletype tt, bool force = false); bool setTiletypeAt(df::coord2d, df::tiletype tt, bool force = false);
uint16_t temperature1At(df::coord2d p) uint16_t temperature1At(df::coord2d p)
{ {
return index_tile<uint16_t>(temp1,p); return index_tile(temp1,p);
} }
bool setTemp1At(df::coord2d p, uint16_t temp) bool setTemp1At(df::coord2d p, uint16_t temp)
{ {
if(!valid) return false; if(!valid) return false;
dirty_temperatures = true; dirty_temperatures = true;
index_tile<uint16_t&>(temp1,p) = temp; index_tile(temp1,p) = temp;
return true; return true;
} }
uint16_t temperature2At(df::coord2d p) uint16_t temperature2At(df::coord2d p)
{ {
return index_tile<uint16_t>(temp2,p); return index_tile(temp2,p);
} }
bool setTemp2At(df::coord2d p, uint16_t temp) bool setTemp2At(df::coord2d p, uint16_t temp)
{ {
if(!valid) return false; if(!valid) return false;
dirty_temperatures = true; dirty_temperatures = true;
index_tile<uint16_t&>(temp2,p) = temp; index_tile(temp2,p) = temp;
return true; return true;
} }
df::tile_designation DesignationAt(df::coord2d p) df::tile_designation DesignationAt(df::coord2d p)
{ {
return index_tile<df::tile_designation>(designation,p); return index_tile(designation,p);
} }
bool setDesignationAt(df::coord2d p, df::tile_designation des) bool setDesignationAt(df::coord2d p, df::tile_designation des, int32_t priority = 4000)
{ {
if(!valid) return false; if(!valid) return false;
dirty_designations = true; dirty_designations = true;
designated_tiles[(p.x&15) + (p.y&15)*16] = true;
//printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y); //printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y);
index_tile<df::tile_designation&>(designation,p) = des; index_tile(designation,p) = des;
if(des.bits.dig && block) if((des.bits.dig || des.bits.smooth) && block) {
block->flags.bits.designated = true; block->flags.bits.designated = true;
setPriorityAt(p, priority);
}
return true; return true;
} }
@ -281,21 +286,21 @@ public:
df::tile_occupancy OccupancyAt(df::coord2d p) df::tile_occupancy OccupancyAt(df::coord2d p)
{ {
return index_tile<df::tile_occupancy>(occupancy,p); return index_tile(occupancy,p);
} }
bool setOccupancyAt(df::coord2d p, df::tile_occupancy des) bool setOccupancyAt(df::coord2d p, df::tile_occupancy des)
{ {
if(!valid) return false; if(!valid) return false;
dirty_occupancies = true; dirty_occupancies = true;
index_tile<df::tile_occupancy&>(occupancy,p) = des; index_tile(occupancy,p) = des;
return true; return true;
} }
bool getFlagAt(df::coord2d p, df::tile_designation::Mask mask) { bool getFlagAt(df::coord2d p, df::tile_designation::Mask mask) {
return (index_tile<df::tile_designation&>(designation,p).whole & mask) != 0; return (index_tile(designation,p).whole & mask) != 0;
} }
bool getFlagAt(df::coord2d p, df::tile_occupancy::Mask mask) { bool getFlagAt(df::coord2d p, df::tile_occupancy::Mask mask) {
return (index_tile<df::tile_occupancy&>(occupancy,p).whole & mask) != 0; return (index_tile(occupancy,p).whole & mask) != 0;
} }
bool setFlagAt(df::coord2d p, df::tile_designation::Mask mask, bool set); bool setFlagAt(df::coord2d p, df::tile_designation::Mask mask, bool set);
bool setFlagAt(df::coord2d p, df::tile_occupancy::Mask mask, bool set); bool setFlagAt(df::coord2d p, df::tile_occupancy::Mask mask, bool set);
@ -303,7 +308,7 @@ public:
int itemCountAt(df::coord2d p) int itemCountAt(df::coord2d p)
{ {
if (!item_counts) init_item_counts(); if (!item_counts) init_item_counts();
return index_tile<int>(item_counts,p); return index_tile(item_counts,p);
} }
t_blockflags BlockFlags() t_blockflags BlockFlags()
@ -316,7 +321,7 @@ public:
int biomeIndexAt(df::coord2d p); int biomeIndexAt(df::coord2d p);
int layerIndexAt(df::coord2d p) { int layerIndexAt(df::coord2d p) {
return index_tile<df::tile_designation&>(designation,p).bits.geolayer_index; return index_tile(designation,p).bits.geolayer_index;
} }
df::coord2d biomeRegionAt(df::coord2d p); df::coord2d biomeRegionAt(df::coord2d p);
@ -342,13 +347,15 @@ private:
void init(); void init();
bool valid; bool valid:1;
bool dirty_designations:1; bool dirty_designations:1;
bool dirty_tiles:1; bool dirty_tiles:1;
bool dirty_veins:1; bool dirty_veins:1;
bool dirty_temperatures:1; bool dirty_temperatures:1;
bool dirty_occupancies:1; bool dirty_occupancies:1;
std::bitset<16*16> designated_tiles;
DFCoord bcoord; DFCoord bcoord;
// Custom tags for floodfill // Custom tags for floodfill
@ -548,13 +555,11 @@ class DFHACK_EXPORT MapCache
return b ? b->DesignationAt(tilecoord) : df::tile_designation(); return b ? b->DesignationAt(tilecoord) : df::tile_designation();
} }
// priority is optional, only set if >= 0 // priority is optional, only set if >= 0
bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = -1) bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = 4000)
{ {
if (Block *b = BlockAtTile(tilecoord)) if (Block *b = BlockAtTile(tilecoord))
{ {
if (!b->setDesignationAt(tilecoord, des)) if (!b->setDesignationAt(tilecoord, des, priority))
return false;
if (priority >= 0 && b->setPriorityAt(tilecoord, priority))
return false; return false;
return true; return true;
} }
@ -599,15 +604,8 @@ class DFHACK_EXPORT MapCache
return b ? b->removeItemOnGround(item) : false; return b ? b->removeItemOnGround(item) : false;
} }
bool WriteAll() bool WriteAll();
{
std::map<DFCoord, Block *>::iterator p;
for(p = blocks.begin(); p != blocks.end(); p++)
{
p->second->Write();
}
return true;
}
void trash() void trash()
{ {
std::map<DFCoord, Block *>::iterator p; std::map<DFCoord, Block *>::iterator p;

@ -167,7 +167,9 @@ typedef uint16_t t_temperatures [16][16];
/** /**
* Index a tile array by a 2D coordinate, clipping it to mod 16 * Index a tile array by a 2D coordinate, clipping it to mod 16
*/ */
template<class R, class T> inline R index_tile(T &v, df::coord2d p) { template<class T> inline auto index_tile(T &v, df::coord2d p)
-> typename std::add_rvalue_reference<decltype(v[0][0])>::type
{
return v[p.x&15][p.y&15]; return v[p.x&15][p.y&15];
} }

@ -31,10 +31,12 @@ distribution.
#include <string> #include <string>
#include <set> #include <set>
#include <memory>
#include "DataDefs.h" #include "DataDefs.h"
#include "df/graphic.h" #include "df/graphic.h"
#include "df/viewscreen.h" #include "df/viewscreen.h"
#include "df/zoom_commands.h"
#include "modules/GuiHooks.h" #include "modules/GuiHooks.h"
@ -182,6 +184,9 @@ namespace DFHack
return rect2d(df::coord2d(0,0), getWindowSize()-df::coord2d(1,1)); return rect2d(df::coord2d(0,0), getWindowSize()-df::coord2d(1,1));
} }
/// Wrapper to call enabler->zoom_display from plugins
DFHACK_EXPORT void zoom(df::zoom_commands cmd);
/// Returns the state of [GRAPHICS:YES/NO] /// Returns the state of [GRAPHICS:YES/NO]
DFHACK_EXPORT bool inGraphicsMode(); DFHACK_EXPORT bool inGraphicsMode();
@ -210,9 +215,9 @@ namespace DFHack
DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL); DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL);
// Push and remove viewscreens // Push and remove viewscreens
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL, Plugin *p = NULL); DFHACK_EXPORT bool show(std::unique_ptr<df::viewscreen> screen, df::viewscreen *before = NULL, Plugin *p = NULL);
inline bool show(df::viewscreen *screen, Plugin *p) inline bool show(std::unique_ptr<df::viewscreen> screen, Plugin *p)
{ return show(screen, NULL, p); } { return show(std::move(screen), NULL, p); }
DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); DFHACK_EXPORT bool isDismissed(df::viewscreen *screen);
DFHACK_EXPORT bool hasActiveScreens(Plugin *p); DFHACK_EXPORT bool hasActiveScreens(Plugin *p);
@ -301,6 +306,15 @@ namespace DFHack
GUI_HOOK_DECLARE(set_tile, bool, (const Pen &pen, int x, int y, bool map)); GUI_HOOK_DECLARE(set_tile, bool, (const Pen &pen, int x, int y, bool map));
} }
//! Temporary hide a screen until destructor is called
struct DFHACK_EXPORT Hide {
Hide(df::viewscreen* screen);
~Hide();
private:
void extract(df::viewscreen*);
void merge(df::viewscreen*);
df::viewscreen* screen_;
};
} }
class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {
@ -374,4 +388,5 @@ namespace DFHack
virtual df::building *getSelectedBuilding(); virtual df::building *getSelectedBuilding();
virtual df::plant *getSelectedPlant(); virtual df::plant *getSelectedPlant();
}; };
} }

@ -139,6 +139,7 @@ DFHACK_EXPORT bool isTamable(df::unit* unit);
DFHACK_EXPORT bool isMale(df::unit* unit); DFHACK_EXPORT bool isMale(df::unit* unit);
DFHACK_EXPORT bool isFemale(df::unit* unit); DFHACK_EXPORT bool isFemale(df::unit* unit);
DFHACK_EXPORT bool isMerchant(df::unit* unit); DFHACK_EXPORT bool isMerchant(df::unit* unit);
DFHACK_EXPORT bool isDiplomat(df::unit* unit);
DFHACK_EXPORT bool isForest(df::unit* unit); DFHACK_EXPORT bool isForest(df::unit* unit);
DFHACK_EXPORT bool isMarkedForSlaughter(df::unit* unit); DFHACK_EXPORT bool isMarkedForSlaughter(df::unit* unit);
DFHACK_EXPORT bool isTame(df::unit* unit); DFHACK_EXPORT bool isTame(df::unit* unit);

@ -180,6 +180,116 @@ function printall_ipairs(table)
end end
end end
local do_print_recurse
local function print_string(printfn, v, seen, indent)
local str = tostring(v)
printfn(str)
return #str;
end
local fill_chars = {
__index = function(table, key, value)
local rv = string.rep(' ', 23 - key) .. ' = '
rawset(table, key, rv)
return rv
end,
}
setmetatable(fill_chars, fill_chars)
local function print_fields(value, seen, indent, prefix)
local ok,f,t,k = pcall(pairs,value)
if not ok then
dfhack.print(prefix)
dfhack.println('<Type doesn\'t support iteration with pairs>')
return 0
end
local prev_value = "not a value"
local repeated = 0
for k, v in f,t,k do
-- Only show set values of bitfields
if value._kind ~= "bitfield" or v then
local continue = false
if type(k) == "number" then
if prev_value == v then
repeated = repeated + 1
continue = true
else
prev_value = v
end
else
prev_value = "not a value"
end
if not continue then
if repeated > 0 then
dfhack.println(prefix .. "<Repeated " .. repeated .. " times>")
repeated = 0
end
dfhack.print(prefix)
local len = do_print_recurse(dfhack.print, k, seen, indent + 1)
dfhack.print(fill_chars[len <= 23 and len or 23])
do_print_recurse(dfhack.println, v, seen, indent + 1)
end
end
end
if repeated > 0 then
dfhack.println(prefix .. "<Repeated " .. repeated .. " times>")
end
return 0
end
-- This should be same as print_array but userdata doesn't compare equal even if
-- they hold same pointer.
local function print_userdata(printfn, value, seen, indent)
local prefix = string.rep(' ', indent)
local strvalue = tostring(value)
dfhack.println(strvalue)
if seen[strvalue] then
dfhack.print(prefix)
dfhack.println('<Cyclic reference! Skipping fields>\n')
return 0
end
seen[strvalue] = true
return print_fields(value, seen, indent, prefix)
end
local function print_array(printfn, value, seen, indent)
local prefix = string.rep(' ', indent)
dfhack.println(tostring(value))
if seen[value] then
dfhack.print(prefix)
dfhack.println('<Cyclic reference! skipping fields>\n')
return 0
end
seen[value] = true
return print_fields(value, seen, indent, prefix)
end
local recurse_type_map = {
number = print_string,
string = print_string,
boolean = print_string,
['function'] = print_string,
['nil'] = print_string,
userdata = print_userdata,
table = print_array,
}
do_print_recurse = function(printfn, value, seen, indent)
local t = type(value)
if not recurse_type_map[t] then
printfn("Unknown type " .. t .. " " .. tostring(value))
return
end
return recurse_type_map[t](printfn, value, seen, indent)
end
function printall_recurse(value)
local seen = {}
do_print_recurse(dfhack.println, value, seen, 0)
end
function copyall(table) function copyall(table)
local rv = {} local rv = {}
for k,v in pairs(table) do rv[k] = v end for k,v in pairs(table) do rv[k] = v end
@ -334,6 +444,7 @@ function dfhack.interpreter(prompt,hfile,env)
" '= foo' => '_1,_2,... = foo'\n".. " '= foo' => '_1,_2,... = foo'\n"..
" '! foo' => 'print(foo)'\n".. " '! foo' => 'print(foo)'\n"..
" '~ foo' => 'printall(foo)'\n".. " '~ foo' => 'printall(foo)'\n"..
" '^ foo' => 'printall_recurse(foo)'\n"..
" '@ foo' => 'printall_ipairs(foo)'\n".. " '@ foo' => 'printall_ipairs(foo)'\n"..
"All of these save the first result as '_'.") "All of these save the first result as '_'.")
print_banner = false print_banner = false
@ -358,6 +469,9 @@ function dfhack.interpreter(prompt,hfile,env)
print(table.unpack(data,2,data.n)) print(table.unpack(data,2,data.n))
printall_ipairs(data[2]) printall_ipairs(data[2])
end, end,
['^'] = function(data)
printall_recurse(data[2])
end,
['='] = function(data) ['='] = function(data)
for i=2,data.n do for i=2,data.n do
local varname = '_'..vcnt local varname = '_'..vcnt

@ -456,15 +456,17 @@ function List:init(info)
end end
function List:setChoices(choices, selected) function List:setChoices(choices, selected)
self.choices = choices or {} self.choices = {}
for i,v in ipairs(self.choices) do for i,v in ipairs(choices or {}) do
local l = utils.clone(v);
if type(v) ~= 'table' then if type(v) ~= 'table' then
v = { text = v } l = { text = v }
self.choices[i] = v else
l.text = v.text or v.caption or v[1]
end end
v.text = v.text or v.caption or v[1] parse_label_text(l)
parse_label_text(v) self.choices[i] = l
end end
self:setSelected(selected) self:setSelected(selected)

@ -505,17 +505,17 @@ function prompt_yes_no(msg,default)
prompt = prompt..' (y/n)[n]: ' prompt = prompt..' (y/n)[n]: '
end end
while true do while true do
local rv = dfhack.lineedit(prompt) local rv,err = dfhack.lineedit(prompt)
if rv then if not rv then
if string.match(rv,'^[Yy]') then qerror(err);
return true elseif string.match(rv,'^[Yy]') then
elseif string.match(rv,'^[Nn]') then return true
return false elseif string.match(rv,'^[Nn]') then
elseif rv == 'abort' then return false
qerror('User abort') elseif rv == 'abort' then
elseif rv == '' and default ~= nil then qerror('User abort')
return default elseif rv == '' and default ~= nil then
end return default
end end
end end
end end
@ -524,7 +524,10 @@ end
function prompt_input(prompt,check,quit_str) function prompt_input(prompt,check,quit_str)
quit_str = quit_str or '~~~' quit_str = quit_str or '~~~'
while true do while true do
local rv = dfhack.lineedit(prompt) local rv,err = dfhack.lineedit(prompt)
if not rv then
qerror(err);
end
if rv == quit_str then if rv == quit_str then
qerror('User abort') qerror('User abort')
end end

@ -676,7 +676,7 @@ static void manageSyndromeEvent(color_ostream& out) {
for ( auto a = df::global::world->units.all.begin(); a != df::global::world->units.all.end(); a++ ) { for ( auto a = df::global::world->units.all.begin(); a != df::global::world->units.all.end(); a++ ) {
df::unit* unit = *a; df::unit* unit = *a;
/* /*
if ( unit->flags1.bits.dead ) if ( unit->flags1.bits.inactive )
continue; continue;
*/ */
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) { for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
@ -723,7 +723,7 @@ static void manageEquipmentEvent(color_ostream& out) {
itemIdToInventoryItem.clear(); itemIdToInventoryItem.clear();
currentlyEquipped.clear(); currentlyEquipped.clear();
df::unit* unit = *a; df::unit* unit = *a;
/*if ( unit->flags1.bits.dead ) /*if ( unit->flags1.bits.inactive )
continue; continue;
*/ */
@ -952,7 +952,7 @@ static void manageUnitAttackEvent(color_ostream& out) {
} }
if ( !wound1 && !wound2 ) { if ( !wound1 && !wound2 ) {
//if ( unit1->flags1.bits.dead || unit2->flags1.bits.dead ) //if ( unit1->flags1.bits.inactive || unit2->flags1.bits.inactive )
// continue; // continue;
if ( reportStr.find("severed part") ) if ( reportStr.find("severed part") )
continue; continue;

@ -33,7 +33,7 @@ void Kitchen::debug_print(color_ostream &out)
out.print("Kitchen Exclusions\n"); out.print("Kitchen Exclusions\n");
for(std::size_t i = 0; i < size(); ++i) for(std::size_t i = 0; i < size(); ++i)
{ {
out.print("%2u: IT:%2i IS:%i MT:%3i MI:%2i ET:%i %s\n", out.print("%2zu: IT:%2i IS:%i MT:%3i MI:%2i ET:%i %s\n",
i, i,
ui->kitchen.item_types[i], ui->kitchen.item_types[i],
ui->kitchen.item_subtypes[i], ui->kitchen.item_subtypes[i],

@ -45,6 +45,7 @@ using namespace std;
#include "modules/Buildings.h" #include "modules/Buildings.h"
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Job.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "df/block_burrow.h" #include "df/block_burrow.h"
@ -57,6 +58,7 @@ using namespace std;
#include "df/burrow.h" #include "df/burrow.h"
#include "df/feature_init.h" #include "df/feature_init.h"
#include "df/flow_info.h" #include "df/flow_info.h"
#include "df/job.h"
#include "df/plant.h" #include "df/plant.h"
#include "df/plant_tree_info.h" #include "df/plant_tree_info.h"
#include "df/plant_tree_tile.h" #include "df/plant_tree_tile.h"
@ -91,7 +93,9 @@ const BiomeInfo MapCache::biome_stub = {
#define COPY(a,b) memcpy(&a,&b,sizeof(a)) #define COPY(a,b) memcpy(&a,&b,sizeof(a))
MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) :
parent(parent),
designated_tiles{}
{ {
dirty_designations = false; dirty_designations = false;
dirty_tiles = false; dirty_tiles = false;
@ -232,7 +236,7 @@ MapExtras::Block::BasematInfo::BasematInfo()
bool MapExtras::Block::setFlagAt(df::coord2d p, df::tile_designation::Mask mask, bool set) bool MapExtras::Block::setFlagAt(df::coord2d p, df::tile_designation::Mask mask, bool set)
{ {
if(!valid) return false; if(!valid) return false;
auto &val = index_tile<df::tile_designation&>(designation,p); auto &val = index_tile(designation,p);
bool cur = (val.whole & mask) != 0; bool cur = (val.whole & mask) != 0;
if (cur != set) if (cur != set)
{ {
@ -245,7 +249,7 @@ bool MapExtras::Block::setFlagAt(df::coord2d p, df::tile_designation::Mask mask,
bool MapExtras::Block::setFlagAt(df::coord2d p, df::tile_occupancy::Mask mask, bool set) bool MapExtras::Block::setFlagAt(df::coord2d p, df::tile_occupancy::Mask mask, bool set)
{ {
if(!valid) return false; if(!valid) return false;
auto &val = index_tile<df::tile_occupancy&>(occupancy,p); auto &val = index_tile(occupancy,p);
bool cur = (val.whole & mask) != 0; bool cur = (val.whole & mask) != 0;
if (cur != set) if (cur != set)
{ {
@ -1063,7 +1067,7 @@ int MapExtras::Block::biomeIndexAt(df::coord2d p)
if (!block) if (!block)
return -1; return -1;
auto des = index_tile<df::tile_designation>(designation,p); auto des = index_tile(designation,p);
uint8_t idx = des.bits.biome; uint8_t idx = des.bits.biome;
if (idx >= 9) if (idx >= 9)
return -1; return -1;
@ -1141,12 +1145,12 @@ bool MapExtras::Block::addItemOnGround(df::item *item)
if (inserted) if (inserted)
{ {
int &count = index_tile<int&>(item_counts,item->pos); int &count = index_tile(item_counts,item->pos);
if (count++ == 0) if (count++ == 0)
{ {
index_tile<df::tile_occupancy&>(occupancy,item->pos).bits.item = true; index_tile(occupancy,item->pos).bits.item = true;
index_tile<df::tile_occupancy&>(block->occupancy,item->pos).bits.item = true; index_tile(block->occupancy,item->pos).bits.item = true;
} }
} }
@ -1166,13 +1170,13 @@ bool MapExtras::Block::removeItemOnGround(df::item *item)
vector_erase_at(block->items, idx); vector_erase_at(block->items, idx);
int &count = index_tile<int&>(item_counts,item->pos); int &count = index_tile(item_counts,item->pos);
if (--count == 0) if (--count == 0)
{ {
index_tile<df::tile_occupancy&>(occupancy,item->pos).bits.item = false; index_tile(occupancy,item->pos).bits.item = false;
auto &occ = index_tile<df::tile_occupancy&>(block->occupancy,item->pos); auto &occ = index_tile(block->occupancy,item->pos);
occ.bits.item = false; occ.bits.item = false;
@ -1250,6 +1254,38 @@ MapExtras::MapCache::MapCache()
} }
} }
bool MapExtras::MapCache::WriteAll()
{
auto world = df::global::world;
df::job_list_link* job_link = world->jobs.list.next;
df::job_list_link* next = nullptr;
for (;job_link;job_link = next) {
next = job_link->next;
df::job* job = job_link->item;
df::coord pos = job->pos;
df::coord blockpos(pos.x>>4,pos.y>>4,pos.z);
auto iter = blocks.find(blockpos);
if (iter == blocks.end())
continue;
df::coord2d bpos(pos.x - (blockpos.x<<4),pos.y - (blockpos.y<<4));
auto block = iter->second;
if (!block->designated_tiles.test(bpos.x+bpos.y*16))
continue;
bool is_designed = ENUM_ATTR(job_type,is_designation,job->job_type);
if (!is_designed)
continue;
// Remove designation job. DF will create a new one in the next tick
// processing.
Job::removeJob(job);
}
std::map<DFCoord, Block *>::iterator p;
for(p = blocks.begin(); p != blocks.end(); p++)
{
p->second->Write();
}
return true;
}
MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord) MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord)
{ {
if(!valid) if(!valid)

@ -504,7 +504,7 @@ df::coord2d Maps::getBlockTileBiomeRgn(df::map_block *block, df::coord2d pos)
if (!block || !world->world_data) if (!block || !world->world_data)
return df::coord2d(); return df::coord2d();
auto des = index_tile<df::tile_designation>(block->designation,pos); auto des = index_tile(block->designation,pos);
unsigned idx = des.bits.biome; unsigned idx = des.bits.biome;
if (idx < 9) if (idx < 9)
{ {
@ -579,8 +579,8 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2)
if (!block1 || !block2) if (!block1 || !block2)
return false; return false;
auto tile1 = index_tile<uint16_t>(block1->walkable, pos1); auto tile1 = index_tile(block1->walkable, pos1);
auto tile2 = index_tile<uint16_t>(block2->walkable, pos2); auto tile2 = index_tile(block2->walkable, pos2);
return tile1 && tile1 == tile2; return tile1 && tile1 == tile2;
} }
@ -607,7 +607,7 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
if ( !block1 || !block2 ) if ( !block1 || !block2 )
return false; return false;
if ( !index_tile<uint16_t>(block1->walkable,pos1) || !index_tile<uint16_t>(block2->walkable,pos2) ) { if ( !index_tile(block1->walkable,pos1) || !index_tile(block2->walkable,pos2) ) {
return false; return false;
} }
@ -626,7 +626,7 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
if ( dx == 0 && dy == 0 ) { if ( dx == 0 && dy == 0 ) {
//check for forbidden hatches and floors and such //check for forbidden hatches and floors and such
df::tile_building_occ upOcc = index_tile<df::tile_occupancy>(block2->occupancy,pos2).bits.building; df::tile_building_occ upOcc = index_tile(block2->occupancy,pos2).bits.building;
if ( upOcc == tile_building_occ::Impassable || upOcc == tile_building_occ::Obstacle || upOcc == tile_building_occ::Floored ) if ( upOcc == tile_building_occ::Impassable || upOcc == tile_building_occ::Obstacle || upOcc == tile_building_occ::Floored )
return false; return false;
@ -659,7 +659,7 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
return false; //unusable ramp return false; //unusable ramp
//there has to be an unforbidden hatch above the ramp //there has to be an unforbidden hatch above the ramp
if ( index_tile<df::tile_occupancy>(block2->occupancy,pos2).bits.building != tile_building_occ::Dynamic ) if ( index_tile(block2->occupancy,pos2).bits.building != tile_building_occ::Dynamic )
return false; return false;
//note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy //note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy
df::building* building = Buildings::findAtTile(pos2); df::building* building = Buildings::findAtTile(pos2);
@ -703,7 +703,7 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
if ( !blockUp ) if ( !blockUp )
return false; return false;
df::tile_building_occ occupancy = index_tile<df::tile_occupancy>(blockUp->occupancy,up).bits.building; df::tile_building_occ occupancy = index_tile(blockUp->occupancy,up).bits.building;
if ( occupancy == tile_building_occ::Obstacle || occupancy == tile_building_occ::Floored || occupancy == tile_building_occ::Impassable ) if ( occupancy == tile_building_occ::Obstacle || occupancy == tile_building_occ::Floored || occupancy == tile_building_occ::Impassable )
return false; return false;
return true; return true;

@ -90,6 +90,10 @@ df::coord2d Screen::getWindowSize()
return df::coord2d(gps->dimx, gps->dimy); return df::coord2d(gps->dimx, gps->dimy);
} }
void Screen::zoom(df::zoom_commands cmd) {
enabler->zoom_display(cmd);
}
bool Screen::inGraphicsMode() bool Screen::inGraphicsMode()
{ {
return init && init->display.flag.is_set(init_display_flags::USE_GRAPHICS); return init && init->display.flag.is_set(init_display_flags::USE_GRAPHICS);
@ -299,7 +303,7 @@ bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *pt
static std::map<df::viewscreen*, Plugin*> plugin_screens; static std::map<df::viewscreen*, Plugin*> plugin_screens;
bool Screen::show(df::viewscreen *screen, df::viewscreen *before, Plugin *plugin) bool Screen::show(std::unique_ptr<df::viewscreen> screen, df::viewscreen *before, Plugin *plugin)
{ {
CHECK_NULL_POINTER(screen); CHECK_NULL_POINTER(screen);
CHECK_INVALID_ARGUMENT(!screen->parent && !screen->child); CHECK_INVALID_ARGUMENT(!screen->parent && !screen->child);
@ -316,15 +320,16 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before, Plugin *plugin
screen->child = parent->child; screen->child = parent->child;
screen->parent = parent; screen->parent = parent;
parent->child = screen; df::viewscreen* s = screen.release();
if (screen->child) parent->child = s;
screen->child->parent = screen; if (s->child)
s->child->parent = s;
if (dfhack_viewscreen::is_instance(screen)) if (dfhack_viewscreen::is_instance(s))
static_cast<dfhack_viewscreen*>(screen)->onShow(); static_cast<dfhack_viewscreen*>(s)->onShow();
if (plugin) if (plugin)
plugin_screens[screen] = plugin; plugin_screens[s] = plugin;
return true; return true;
} }
@ -371,6 +376,42 @@ bool Screen::hasActiveScreens(Plugin *plugin)
return false; return false;
} }
namespace DFHack { namespace Screen {
Hide::Hide(df::viewscreen* screen) :
screen_{screen}
{
extract(screen_);
}
Hide::~Hide()
{
if (screen_)
merge(screen_);
}
void Hide::extract(df::viewscreen* a)
{
df::viewscreen* ap = a->parent;
df::viewscreen* ac = a->child;
ap->child = ac;
if (ac) ac->parent = ap;
else Core::getInstance().top_viewscreen = ap;
}
void Hide::merge(df::viewscreen* a)
{
df::viewscreen* ap = a->parent;
df::viewscreen* ac = a->parent->child;
ap->child = a;
a->child = ac;
if (ac) ac->parent = a;
else Core::getInstance().top_viewscreen = a;
}
} }
#ifdef _LINUX #ifdef _LINUX
class DFHACK_EXPORT renderer { class DFHACK_EXPORT renderer {
unsigned char *screen; unsigned char *screen;

@ -219,15 +219,9 @@ df::language_name *Units::getVisibleName(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
// as of 0.44.11, identity names take precedence over associated histfig names
if (auto identity = getIdentity(unit)) if (auto identity = getIdentity(unit))
{
auto id_hfig = df::historical_figure::find(identity->histfig_id);
if (id_hfig)
return &id_hfig->name;
return &identity->name; return &identity->name;
}
return &unit->name; return &unit->name;
} }
@ -1476,6 +1470,13 @@ bool Units::isMerchant(df::unit* unit)
return unit->flags1.bits.merchant == 1; return unit->flags1.bits.merchant == 1;
} }
bool Units::isDiplomat(df::unit* unit)
{
CHECK_NULL_POINTER(unit);
return unit->flags1.bits.diplomat == 1;
}
bool Units::isForest(df::unit* unit) bool Units::isForest(df::unit* unit)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
@ -1579,7 +1580,7 @@ bool Units::isActive(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
return !unit->flags1.bits.dead; return !unit->flags1.bits.inactive;
} }
bool Units::isKilled(df::unit *unit) bool Units::isKilled(df::unit *unit)

@ -1 +1 @@
Subproject commit c3025feb80c6f8e7441ea5dcf4f463a9cf89cbbd Subproject commit 372993c58851153320f3390119cdd2d97caef9db

@ -61,7 +61,12 @@ fi
export LD_LIBRARY_PATH="./hack/libs:./hack:$LD_LIBRARY_PATH" export LD_LIBRARY_PATH="./hack/libs:./hack:$LD_LIBRARY_PATH"
PRELOAD_LIB="${PRELOAD_LIB:+$PRELOAD_LIB:}./hack/libdfhack.so" LIB="./hack/libdfhack.so"
LIBSAN=""
if which objdump > /dev/null; then
LIBSAN="$(objdump -p $LIB | sed -n 's/^.*NEEDED.*\(lib[a-z]san[a-z.0-9]*\).*$/\1/p' | head -n1):"
fi
PRELOAD_LIB="${PRELOAD_LIB:+$PRELOAD_LIB:}${LIBSAN}${LIB}"
setarch_arch=$(cat hack/dfhack_setarch.txt || printf i386) setarch_arch=$(cat hack/dfhack_setarch.txt || printf i386)

@ -589,7 +589,7 @@ bool VeinGenerator::init_biomes()
if (info.geo_index < 0 || !info.geobiome) if (info.geo_index < 0 || !info.geobiome)
{ {
out.printerr("Biome %d is not defined.\n", i); out.printerr("Biome %zd is not defined.\n", i);
return false; return false;
} }
@ -1567,7 +1567,7 @@ bool VeinGenerator::place_veins(bool verbose)
sort(queue.begin(), queue.end(), vein_cmp); sort(queue.begin(), queue.end(), vein_cmp);
// Place tiles // Place tiles
out.print("Processing... ", queue.size()); out.print("Processing... (%zu)", queue.size());
for (size_t j = 0; j < queue.size(); j++) for (size_t j = 0; j < queue.size(); j++)
{ {
@ -1588,7 +1588,7 @@ bool VeinGenerator::place_veins(bool verbose)
out.print("done."); out.print("done.");
out.print( out.print(
"\nVein layer %d of %d: %s %s (%.2f%%)... ", "\nVein layer %zu of %zu: %s %s (%.2f%%)... ",
j+1, queue.size(), j+1, queue.size(),
MaterialInfo(0,queue[j]->vein.first).getToken().c_str(), MaterialInfo(0,queue[j]->vein.first).getToken().c_str(),
ENUM_KEY_STR(inclusion_type, queue[j]->vein.second).c_str(), ENUM_KEY_STR(inclusion_type, queue[j]->vein.second).c_str(),
@ -1597,7 +1597,7 @@ bool VeinGenerator::place_veins(bool verbose)
} }
else else
{ {
out.print("\rVein layer %d of %d... ", j+1, queue.size()); out.print("\rVein layer %zu of %zu... ", j+1, queue.size());
out.flush(); out.flush();
} }

@ -214,7 +214,7 @@ DFHack::command_result parseRectangle(DFHack::color_ostream & out,
bool hasConsole = true) bool hasConsole = true)
{ {
using namespace DFHack; using namespace DFHack;
int newWidth = 0, newHeight = 0, newZLevels = 0; int newWidth = 0, newHeight = 0, newZLevels = 0, rv = 0;
if (end > start + 1) if (end > start + 1)
{ {
@ -237,7 +237,10 @@ DFHack::command_result parseRectangle(DFHack::color_ostream & out,
str.str(""); str.str("");
str << "Set range width <" << width << "> "; str << "Set range width <" << width << "> ";
con.lineedit(str.str(), command, hist); while ((rv = con.lineedit(str.str(), command, hist))
== Console::RETRY);
if (rv <= Console::FAILURE)
return rv == Console::FAILURE ? CR_FAILURE : CR_FAILURE;
hist.add(command); hist.add(command);
newWidth = command.empty() ? width : atoi(command.c_str()); newWidth = command.empty() ? width : atoi(command.c_str());
} else { } else {
@ -251,7 +254,10 @@ DFHack::command_result parseRectangle(DFHack::color_ostream & out,
str.str(""); str.str("");
str << "Set range height <" << height << "> "; str << "Set range height <" << height << "> ";
con.lineedit(str.str(), command, hist); while ((rv = con.lineedit(str.str(), command, hist))
== Console::RETRY);
if (rv <= Console::FAILURE)
return rv == Console::FAILURE ? CR_FAILURE : CR_OK;
hist.add(command); hist.add(command);
newHeight = command.empty() ? height : atoi(command.c_str()); newHeight = command.empty() ? height : atoi(command.c_str());
} else { } else {
@ -265,7 +271,10 @@ DFHack::command_result parseRectangle(DFHack::color_ostream & out,
str.str(""); str.str("");
str << "Set range z-levels <" << zLevels << "> "; str << "Set range z-levels <" << zLevels << "> ";
con.lineedit(str.str(), command, hist); while ((rv = con.lineedit(str.str(), command, hist))
== Console::RETRY);
if (rv <= Console::FAILURE)
return rv == Console::FAILURE ? CR_FAILURE : CR_OK;
hist.add(command); hist.add(command);
newZLevels = command.empty() ? zLevels : atoi(command.c_str()); newZLevels = command.empty() ? zLevels : atoi(command.c_str());
} else { } else {

@ -85,7 +85,7 @@ if (BUILD_SUPPORTED)
#DFHACK_PLUGIN(advtools advtools.cpp) #DFHACK_PLUGIN(advtools advtools.cpp)
DFHACK_PLUGIN(autochop autochop.cpp) DFHACK_PLUGIN(autochop autochop.cpp)
DFHACK_PLUGIN(autodump autodump.cpp) DFHACK_PLUGIN(autodump autodump.cpp)
DFHACK_PLUGIN(autogems autogems.cpp LINK_LIBRARIES jsoncpp) DFHACK_PLUGIN(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static)
DFHACK_PLUGIN(autohauler autohauler.cpp) DFHACK_PLUGIN(autohauler autohauler.cpp)
DFHACK_PLUGIN(autolabor autolabor.cpp) DFHACK_PLUGIN(autolabor autolabor.cpp)
DFHACK_PLUGIN(automaterial automaterial.cpp) DFHACK_PLUGIN(automaterial automaterial.cpp)
@ -105,6 +105,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(confirm confirm.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(confirm confirm.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(createitem createitem.cpp) DFHACK_PLUGIN(createitem createitem.cpp)
DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp)
DFHACK_PLUGIN(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(deramp deramp.cpp) DFHACK_PLUGIN(deramp deramp.cpp)
DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(dig dig.cpp)
DFHACK_PLUGIN(digFlood digFlood.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp)
@ -137,7 +138,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(misery misery.cpp)
DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mode mode.cpp)
DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp)
DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp) DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static)
DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp)
DFHACK_PLUGIN(plants plants.cpp) DFHACK_PLUGIN(plants plants.cpp)

@ -482,9 +482,9 @@ static void printCompanionHeader(color_ostream &out, size_t i, df::unit *unit)
out << i; out << i;
out << ": " << getUnitNameProfession(unit); out << ": " << getUnitNameProfession(unit);
if (unit->flags1.bits.dead) if (Units::isDead(unit))
out << " (DEAD)"; out << " (DEAD)";
if (unit->flags3.bits.ghostly) if (Units::isGhost(unit))
out << " (GHOST)"; out << " (GHOST)";
out << endl; out << endl;

@ -809,7 +809,7 @@ struct autochop_hook : public df::viewscreen_dwarfmodest
if (isInDesignationMenu() && input->count(interface_key::CUSTOM_C)) if (isInDesignationMenu() && input->count(interface_key::CUSTOM_C))
{ {
sendKey(interface_key::LEAVESCREEN); sendKey(interface_key::LEAVESCREEN);
Screen::show(new ViewscreenAutochop(), plugin_self); Screen::show(dts::make_unique<ViewscreenAutochop>(), plugin_self);
} }
else else
{ {
@ -852,7 +852,7 @@ command_result df_autochop (color_ostream &out, vector <string> & parameters)
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
if (Maps::IsValid()) if (Maps::IsValid())
Screen::show(new ViewscreenAutochop(), plugin_self); Screen::show(dts::make_unique<ViewscreenAutochop>(), plugin_self);
return CR_OK; return CR_OK;
} }

@ -222,7 +222,8 @@ void create_jobs() {
} }
DFhackCExport command_result plugin_onupdate(color_ostream &out) { DFhackCExport command_result plugin_onupdate(color_ostream &out) {
if (running && !World::ReadPauseState() && (world->frame_counter % DELTA_TICKS == 0)) { if (running && !World::ReadPauseState() && Maps::IsValid() &&
(world->frame_counter % DELTA_TICKS == 0)) {
create_jobs(); create_jobs();
} }

@ -1497,7 +1497,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
if (maximum < minimum || maximum < 0 || minimum < 0) if (maximum < minimum || maximum < 0 || minimum < 0)
{ {
out.printerr("Syntax: autolabor <labor> <minimum> [<maximum>] [<talent pool>]\n", maximum, minimum); out.printerr("Syntax: autolabor <labor> <minimum> [<maximum>] [<talent pool>], %d > %d\n", maximum, minimum);
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }

@ -156,7 +156,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
} }
else if (input->count(interface_key::CUSTOM_SHIFT_M)) else if (input->count(interface_key::CUSTOM_SHIFT_M))
{ {
Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type)), plugin_self); Screen::show(dts::make_unique<ViewscreenChooseMaterial>(planner.getDefaultItemFilterForType(type)), plugin_self);
} }
else if (input->count(interface_key::CUSTOM_Q)) else if (input->count(interface_key::CUSTOM_Q))
{ {

@ -295,7 +295,7 @@ static void init_map(color_ostream &out)
active = true; active = true;
if (auto_grow && !grow_burrows.empty()) if (auto_grow && !grow_burrows.empty())
out.print("Auto-growing %d burrows.\n", grow_burrows.size()); out.print("Auto-growing %zu burrows.\n", grow_burrows.size());
} }
static void deinit_map(color_ostream &out) static void deinit_map(color_ostream &out)

@ -120,12 +120,12 @@ command_result changelayer (color_ostream &out, std::vector <std::string> & para
{ {
if(parameters[i] == "help" || parameters[i] == "?") if(parameters[i] == "help" || parameters[i] == "?")
{ {
out.print(changelayer_help.c_str()); out.print("%s",changelayer_help.c_str());
return CR_OK; return CR_OK;
} }
if(parameters[i] == "trouble") if(parameters[i] == "trouble")
{ {
out.print(changelayer_trouble.c_str()); out.print("%s",changelayer_trouble.c_str());
return CR_OK; return CR_OK;
} }
if(parameters[i] == "force") if(parameters[i] == "force")

@ -87,7 +87,7 @@ command_result df_cleanowned (color_ostream &out, vector <string> & parameters)
return CR_FAILURE; return CR_FAILURE;
} }
out.print("Found total %d items.\n", world->items.all.size()); out.print("Found total %zd items.\n", world->items.all.size());
for (std::size_t i=0; i < world->items.all.size(); i++) for (std::size_t i=0; i < world->items.all.size(); i++)
{ {
@ -160,7 +160,7 @@ command_result df_cleanowned (color_ostream &out, vector <string> & parameters)
std::string description; std::string description;
item->getItemDescription(&description, 0); item->getItemDescription(&description, 0);
out.print( out.print(
"0x%x %s (wear %d)", "0x%p %s (wear %d)",
item, item,
description.c_str(), description.c_str(),
item->getWear() item->getWear()

@ -200,7 +200,10 @@ void viewscreen_commandpromptst::submit()
return; return;
submitted = true; submitted = true;
prompt_ostream out(this); prompt_ostream out(this);
Core::getInstance().runCommand(out, get_entry()); {
Screen::Hide hide_guard(this);
Core::getInstance().runCommand(out, get_entry());
}
if(out.empty() && responses.empty()) if(out.empty() && responses.empty())
Screen::dismiss(this); Screen::dismiss(this);
else else
@ -312,7 +315,7 @@ void viewscreen_commandpromptst::feed(std::set<df::interface_key> *events)
command_result show_prompt(color_ostream &out, std::vector <std::string> & parameters) command_result show_prompt(color_ostream &out, std::vector <std::string> & parameters)
{ {
if (Gui::getCurFocus() == "dfhack/commandprompt") if (Gui::getCurFocus(true) == "dfhack/commandprompt")
{ {
Screen::dismiss(Gui::getCurViewscreen(true)); Screen::dismiss(Gui::getCurViewscreen(true));
return CR_OK; return CR_OK;
@ -320,7 +323,7 @@ command_result show_prompt(color_ostream &out, std::vector <std::string> & param
std::string params; std::string params;
for(size_t i=0;i<parameters.size();i++) for(size_t i=0;i<parameters.size();i++)
params+=parameters[i]+" "; params+=parameters[i]+" ";
Screen::show(new viewscreen_commandpromptst(params), plugin_self); Screen::show(dts::make_unique<viewscreen_commandpromptst>(params), plugin_self);
return CR_OK; return CR_OK;
} }
bool hotkey_allow_all(df::viewscreen *top) bool hotkey_allow_all(df::viewscreen *top)

@ -160,8 +160,9 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
{ {
df::unit * unit = world->units.all[i]; df::unit * unit = world->units.all[i];
// filter out all "living" units that are currently removed from play
// don't spam all completely dead creatures if not explicitly wanted // don't spam all completely dead creatures if not explicitly wanted
if(unit->flags1.bits.dead && ignoreDead) if((!Units::isActive(unit) && !Units::isKilled(unit)) || (Units::isKilled(unit) && ignoreDead))
{ {
continue; continue;
} }
@ -217,7 +218,7 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
cursetype.c_str(), cursetype.c_str(),
// technically most cursed creatures are undead, // technically most cursed creatures are undead,
// therefore output 'active' if they are not completely dead // therefore output 'active' if they are not completely dead
unit->flags1.bits.dead ? "deceased" : "active", unit->flags2.bits.killed ? "deceased" : "active",
unit->flags3.bits.ghostly ? "-ghostly" : "", unit->flags3.bits.ghostly ? "-ghostly" : "",
missing ? "-missing" : "" missing ? "-missing" : ""
); );
@ -239,9 +240,9 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
} }
if (checkWholeMap) if (checkWholeMap)
out.print("Number of cursed creatures on map: %d \n", cursecount); out.print("Number of cursed creatures on map: %zd \n", cursecount);
else else
out.print("Number of cursed creatures on tile: %d \n", cursecount); out.print("Number of cursed creatures on tile: %zd \n", cursecount);
return CR_OK; return CR_OK;
} }

@ -0,0 +1,274 @@
/* Plugin for exporting C++11 random number functionality
*Exports functions for random number generation
*Functions:
- seedRNG(seed)
- rollInt(min, max)
- rollDouble(min, max)
- rollNormal(mean, std_deviation)
- rollBool(chance_for_true)
- resetIndexRolls(string, array_length) --String identifies the instance of SimpleNumDistribution to reset
- rollIndex(string, array_length) --String identifies the instance of SimpleNumDistribution to use
--(Shuffles a vector of indices, Next() increments through then reshuffles when end() is reached)
Author: Josh Cooper
Created: Dec. 13 2017
Updated: Dec. 21 2017
*/
#include <random>
#include <chrono>
#include <unordered_map>
#include <vector>
#include <algorithm>
#include <cstdint>
#include <cinttypes>
#include <stdio.h>
#include <stdlib.h>
#include "Error.h"
#include "Core.h"
#include "DataFuncs.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
using namespace DFHack;
DFHACK_PLUGIN("cxxrandom");
#define PLUGIN_VERSION 2.0
color_ostream *cout = nullptr;
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
cout = &out;
return CR_OK;
}
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
return CR_OK;
}
class EnginesKeeper
{
private:
EnginesKeeper() {}
std::unordered_map<uint16_t, std::mt19937_64> m_engines;
uint16_t counter = 0;
public:
static EnginesKeeper& Instance()
{
static EnginesKeeper instance;
return instance;
}
uint16_t NewEngine( uint64_t seed )
{
std::mt19937_64 engine( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() );
m_engines[++counter] = engine;
return counter;
}
void DestroyEngine( uint16_t id )
{
m_engines.erase( id );
}
void NewSeed( uint16_t id, uint64_t seed )
{
CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() );
m_engines[id].seed( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() );
}
std::mt19937_64& RNG( uint16_t id )
{
CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() );
return m_engines[id];
}
};
uint16_t GenerateEngine( uint64_t seed )
{
return EnginesKeeper::Instance().NewEngine( seed );
}
void DestroyEngine( uint16_t id )
{
EnginesKeeper::Instance().DestroyEngine( id );
}
void NewSeed( uint16_t id, uint64_t seed )
{
EnginesKeeper::Instance().NewSeed( id, seed );
}
int rollInt(uint16_t id, int min, int max)
{
std::uniform_int_distribution<int> ND(min, max);
return ND(EnginesKeeper::Instance().RNG(id));
}
double rollDouble(uint16_t id, double min, double max)
{
std::uniform_real_distribution<double> ND(min, max);
return ND(EnginesKeeper::Instance().RNG(id));
}
double rollNormal(uint16_t id, double mean, double stddev)
{
std::normal_distribution<double> ND(mean, stddev);
return ND(EnginesKeeper::Instance().RNG(id));
}
bool rollBool(uint16_t id, float p)
{
std::bernoulli_distribution ND(p);
return ND(EnginesKeeper::Instance().RNG(id));
}
class NumberSequence
{
private:
unsigned short m_position = 0;
std::vector<int64_t> m_numbers;
public:
NumberSequence(){}
NumberSequence( int64_t start, int64_t end )
{
for( int64_t i = start; i <= end; ++i )
{
m_numbers.push_back( i );
}
}
void Add( int64_t num ) { m_numbers.push_back( num ); }
void Reset() { m_numbers.clear(); }
int64_t Next()
{
if(m_position >= m_numbers.size())
{
m_position = 0;
}
return m_numbers[m_position++];
}
void Shuffle( uint16_t id )
{
std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG( id ) );
}
void Print()
{
char buffer1[256] = {0};
char buffer2[256] = {0};
for( auto v : m_numbers )
{
sprintf( buffer2, "%s%" PRId64, buffer1, v );
sprintf( buffer1, "%s ", buffer2 );
}
cout->print( "%s", buffer1 );
}
};
class SequenceKeeper
{
private:
SequenceKeeper() {}
std::unordered_map<uint16_t, NumberSequence> m_sequences;
uint16_t counter = 0;
public:
static SequenceKeeper& Instance()
{
static SequenceKeeper instance;
return instance;
}
uint16_t MakeNumSequence( int64_t start, int64_t end )
{
m_sequences[++counter] = NumberSequence( start, end );
return counter;
}
uint16_t MakeNumSequence()
{
m_sequences[++counter] = NumberSequence();
return counter;
}
void DestroySequence( uint16_t id )
{
m_sequences.erase( id );
}
void AddToSequence( uint16_t id, int64_t num )
{
CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() );
m_sequences[id].Add( num );
}
void Shuffle( uint16_t id, uint16_t rng_id )
{
CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() );
m_sequences[id].Shuffle( rng_id );
}
int64_t NextInSequence( uint16_t id )
{
CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() );
return m_sequences[id].Next();
}
void PrintSequence( uint16_t id )
{
CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() );
auto seq = m_sequences[id];
seq.Print();
}
};
uint16_t MakeNumSequence( int64_t start, int64_t end )
{
if( start == end )
{
return SequenceKeeper::Instance().MakeNumSequence();
}
return SequenceKeeper::Instance().MakeNumSequence( start, end );
}
void DestroyNumSequence( uint16_t id )
{
SequenceKeeper::Instance().DestroySequence( id );
}
void AddToSequence( uint16_t id, int64_t num )
{
SequenceKeeper::Instance().AddToSequence( id, num );
}
void ShuffleSequence( uint16_t rngID, uint16_t id )
{
SequenceKeeper::Instance().Shuffle( id, rngID );
}
int64_t NextInSequence( uint16_t id )
{
return SequenceKeeper::Instance().NextInSequence( id );
}
void DebugSequence( uint16_t id )
{
SequenceKeeper::Instance().PrintSequence( id );
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(GenerateEngine),
DFHACK_LUA_FUNCTION(DestroyEngine),
DFHACK_LUA_FUNCTION(NewSeed),
DFHACK_LUA_FUNCTION(rollInt),
DFHACK_LUA_FUNCTION(rollDouble),
DFHACK_LUA_FUNCTION(rollNormal),
DFHACK_LUA_FUNCTION(rollBool),
DFHACK_LUA_FUNCTION(MakeNumSequence),
DFHACK_LUA_FUNCTION(DestroyNumSequence),
DFHACK_LUA_FUNCTION(AddToSequence),
DFHACK_LUA_FUNCTION(ShuffleSequence),
DFHACK_LUA_FUNCTION(NextInSequence),
DFHACK_LUA_FUNCTION(DebugSequence),
DFHACK_LUA_END
};

@ -7,10 +7,12 @@
#include "DataDefs.h" #include "DataDefs.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Job.h"
#include "TileTypes.h" #include "TileTypes.h"
#include "df/map_block.h" #include "df/map_block.h"
#include "df/world.h" #include "df/world.h"
#include "df/job.h"
using std::vector; using std::vector;
using std::string; using std::string;
@ -20,6 +22,24 @@ using namespace df::enums;
DFHACK_PLUGIN("deramp"); DFHACK_PLUGIN("deramp");
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
static void doDeramp(df::map_block* block, df::map_block* above, int x, int y, df::tiletype oldT)
{
// Current tile is a ramp.
// Set current tile, as accurately as can be expected
df::tiletype newT = findSimilarTileType(oldT, tiletype_shape::FLOOR);
// If no change, skip it (couldn't find a good tile type)
if (oldT == newT)
return;
// Set new tile type, clear designation
block->tiletype[x][y] = newT;
block->designation[x][y].bits.dig = tile_dig_designation::No;
// Check the tile above this one, in case a downward slope needs to be removed.
if ((above) && (tileShape(above->tiletype[x][y]) == tiletype_shape::RAMP_TOP))
above->tiletype[x][y] = tiletype::OpenSpace; // open space
}
command_result df_deramp (color_ostream &out, vector <string> & parameters) command_result df_deramp (color_ostream &out, vector <string> & parameters)
{ {
if (!parameters.empty()) if (!parameters.empty())
@ -36,6 +56,23 @@ command_result df_deramp (color_ostream &out, vector <string> & parameters)
int count = 0; int count = 0;
int countbad = 0; int countbad = 0;
df::job_list_link* next;
for (df::job_list_link* jl = world->jobs.list.next;jl; jl = next) {
next = jl->next;
df::job* job = jl->item;
if (job->job_type != df::job_type::RemoveStairs)
continue;
df::map_block *block = Maps::getTileBlock(job->pos);
df::coord2d bpos = job->pos - block->map_pos;
df::tiletype oldT = block->tiletype[bpos.x][bpos.y];
if (tileShape(oldT) != tiletype_shape::RAMP)
continue;
df::map_block *above = Maps::getTileBlock(job->pos + df::coord(0,0,1));
doDeramp(block, above, bpos.x, bpos.y, oldT);
count++;
Job::removeJob(job);
}
int num_blocks = 0, blocks_total = world->map.map_blocks.size(); int num_blocks = 0, blocks_total = world->map.map_blocks.size();
for (int i = 0; i < blocks_total; i++) for (int i = 0; i < blocks_total; i++)
{ {
@ -50,20 +87,7 @@ command_result df_deramp (color_ostream &out, vector <string> & parameters)
if ((tileShape(oldT) == tiletype_shape::RAMP) && if ((tileShape(oldT) == tiletype_shape::RAMP) &&
(block->designation[x][y].bits.dig == tile_dig_designation::Default)) (block->designation[x][y].bits.dig == tile_dig_designation::Default))
{ {
// Current tile is a ramp. doDeramp(block, above, x, y, oldT);
// Set current tile, as accurately as can be expected
df::tiletype newT = findSimilarTileType(oldT, tiletype_shape::FLOOR);
// If no change, skip it (couldn't find a good tile type)
if (oldT == newT)
continue;
// Set new tile type, clear designation
block->tiletype[x][y] = newT;
block->designation[x][y].bits.dig = tile_dig_designation::No;
// Check the tile above this one, in case a downward slope needs to be removed.
if ((above) && (tileShape(above->tiletype[x][y]) == tiletype_shape::RAMP_TOP))
above->tiletype[x][y] = tiletype::OpenSpace; // open space
count++; count++;
} }
// ramp fixer // ramp fixer

@ -113,7 +113,7 @@ command_result eventExample(color_ostream& out, vector<string>& parameters) {
//static int timerCount=0; //static int timerCount=0;
//static int timerDenom=0; //static int timerDenom=0;
void jobInitiated(color_ostream& out, void* job_) { void jobInitiated(color_ostream& out, void* job_) {
out.print("Job initiated! 0x%X\n", job_); out.print("Job initiated! %p\n", job_);
/* /*
df::job* job = (df::job*)job_; df::job* job = (df::job*)job_;
out.print(" completion_timer = %d\n", job->completion_timer); out.print(" completion_timer = %d\n", job->completion_timer);
@ -124,15 +124,15 @@ void jobInitiated(color_ostream& out, void* job_) {
} }
void jobCompleted(color_ostream& out, void* job) { void jobCompleted(color_ostream& out, void* job) {
out.print("Job completed! 0x%X\n", job); out.print("Job completed! %p\n", job);
} }
void timePassed(color_ostream& out, void* ptr) { void timePassed(color_ostream& out, void* ptr) {
out.print("Time: %d\n", (intptr_t)(ptr)); out.print("Time: %zi\n", (intptr_t)(ptr));
} }
void unitDeath(color_ostream& out, void* ptr) { void unitDeath(color_ostream& out, void* ptr) {
out.print("Death: %d\n", (intptr_t)(ptr)); out.print("Death: %zi\n", (intptr_t)(ptr));
} }
void itemCreate(color_ostream& out, void* ptr) { void itemCreate(color_ostream& out, void* ptr) {
@ -143,15 +143,15 @@ void itemCreate(color_ostream& out, void* ptr) {
df::item* item = df::global::world->items.all[item_index]; df::item* item = df::global::world->items.all[item_index];
df::item_type type = item->getType(); df::item_type type = item->getType();
df::coord pos = item->pos; df::coord pos = item->pos;
out.print("Item created: %d, %s, at (%d,%d,%d)\n", (intptr_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); out.print("Item created: %zi, %s, at (%d,%d,%d)\n", (intptr_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z);
} }
void building(color_ostream& out, void* ptr) { void building(color_ostream& out, void* ptr) {
out.print("Building created/destroyed: %d\n", (intptr_t)ptr); out.print("Building created/destroyed: %zi\n", (intptr_t)ptr);
} }
void construction(color_ostream& out, void* ptr) { void construction(color_ostream& out, void* ptr) {
out.print("Construction created/destroyed: 0x%X\n", ptr); out.print("Construction created/destroyed: %p\n", ptr);
df::construction* constr = (df::construction*)ptr; df::construction* constr = (df::construction*)ptr;
df::coord pos = constr->pos; df::coord pos = constr->pos;
out.print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z); out.print(" (%d,%d,%d)\n", pos.x, pos.y, pos.z);
@ -168,7 +168,7 @@ void syndrome(color_ostream& out, void* ptr) {
} }
void invasion(color_ostream& out, void* ptr) { void invasion(color_ostream& out, void* ptr) {
out.print("New invasion! %d\n", (intptr_t)ptr); out.print("New invasion! %zi\n", (intptr_t)ptr);
} }
void unitAttack(color_ostream& out, void* ptr) { void unitAttack(color_ostream& out, void* ptr) {

@ -1,3 +1,4 @@
#include <atomic>
#include <vector> #include <vector>
#include <string> #include <string>
@ -24,13 +25,12 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
//FIXME: possible race conditions with calling kittens from the IO thread and shutdown from Core. std::atomic<bool> shutdown_flag{false};
volatile bool shutdown_flag = false; std::atomic<bool> final_flag{true};
volatile bool final_flag = true; std::atomic<bool> timering{false};
bool timering = false; std::atomic<bool> trackmenu_flg{false};
bool trackmenu_flg = false; std::atomic<uint8_t> trackpos_flg{0};
bool trackpos_flg = false; std::atomic<uint8_t> statetrack{0};
bool statetrack = false;
int32_t last_designation[3] = {-30000, -30000, -30000}; int32_t last_designation[3] = {-30000, -30000, -30000};
int32_t last_mouse[2] = {-1, -1}; int32_t last_mouse[2] = {-1, -1};
df::ui_sidebar_mode last_menu = df::ui_sidebar_mode::Default; df::ui_sidebar_mode last_menu = df::ui_sidebar_mode::Default;
@ -93,14 +93,12 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
if(timering == true) if(timering)
{ {
uint64_t time2 = GetTimeMs64(); uint64_t time2 = GetTimeMs64();
// harmless potential data race here...
uint64_t delta = time2-timeLast; uint64_t delta = time2-timeLast;
// harmless potential data race here...
timeLast = time2; timeLast = time2;
out.print("Time delta = %d ms\n", delta); out.print("Time delta = %d ms\n", int(delta));
} }
if(trackmenu_flg) if(trackmenu_flg)
{ {
@ -135,30 +133,30 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
command_result trackmenu (color_ostream &out, vector <string> & parameters) command_result trackmenu (color_ostream &out, vector <string> & parameters)
{ {
if(trackmenu_flg) bool is_running = trackmenu_flg.exchange(false);
if(is_running)
{ {
trackmenu_flg = false;
return CR_OK; return CR_OK;
} }
else else
{ {
trackmenu_flg = true;
is_enabled = true; is_enabled = true;
last_menu = ui->main.mode; last_menu = ui->main.mode;
out.print("Menu: %d\n",last_menu); out.print("Menu: %d\n",last_menu);
trackmenu_flg = true;
return CR_OK; return CR_OK;
} }
} }
command_result trackpos (color_ostream &out, vector <string> & parameters) command_result trackpos (color_ostream &out, vector <string> & parameters)
{ {
trackpos_flg = !trackpos_flg; trackpos_flg.fetch_xor(1);
is_enabled = true; is_enabled = true;
return CR_OK; return CR_OK;
} }
command_result trackstate ( color_ostream& out, vector< string >& parameters ) command_result trackstate ( color_ostream& out, vector< string >& parameters )
{ {
statetrack = !statetrack; statetrack.fetch_xor(1);
return CR_OK; return CR_OK;
} }
@ -169,10 +167,10 @@ command_result colormods (color_ostream &out, vector <string> & parameters)
for(df::creature_raw* rawlion : vec) for(df::creature_raw* rawlion : vec)
{ {
df::caste_raw * caste = rawlion->caste[0]; df::caste_raw * caste = rawlion->caste[0];
out.print("%s\nCaste addr 0x%x\n",rawlion->creature_id.c_str(), &caste->color_modifiers); out.print("%s\nCaste addr %p\n",rawlion->creature_id.c_str(), &caste->color_modifiers);
for(size_t j = 0; j < caste->color_modifiers.size();j++) for(size_t j = 0; j < caste->color_modifiers.size();j++)
{ {
out.print("mod %d: 0x%x\n", j, caste->color_modifiers[j]); out.print("mod %zd: %p\n", j, caste->color_modifiers[j]);
} }
} }
return CR_OK; return CR_OK;
@ -180,20 +178,19 @@ command_result colormods (color_ostream &out, vector <string> & parameters)
command_result ktimer (color_ostream &out, vector <string> & parameters) command_result ktimer (color_ostream &out, vector <string> & parameters)
{ {
if(timering) bool is_running = timering.exchange(false);
if(is_running)
{ {
timering = false;
return CR_OK; return CR_OK;
} }
uint64_t timestart = GetTimeMs64(); uint64_t timestart = GetTimeMs64();
{ {
CoreSuspender suspend; CoreSuspender suspend;
uint64_t timeend = GetTimeMs64();
timeLast = timeend;
timering = true;
out.print("Time to suspend = %d ms\n", int(timeend - timestart));
} }
uint64_t timeend = GetTimeMs64();
out.print("Time to suspend = %d ms\n",timeend - timestart);
// harmless potential data race here...
timeLast = timeend;
timering = true;
is_enabled = true; is_enabled = true;
return CR_OK; return CR_OK;
} }
@ -255,7 +252,7 @@ command_result kittens (color_ostream &out, vector <string> & parameters)
Console::color_value color = COLOR_BLUE; Console::color_value color = COLOR_BLUE;
while(1) while(1)
{ {
if(shutdown_flag) if(shutdown_flag || !con.isInited())
{ {
final_flag = true; final_flag = true;
con.reset_color(); con.reset_color();

@ -188,7 +188,7 @@ command_result memview (color_ostream &out, vector <string> & parameters)
isValid=true; isValid=true;
if(!isValid) if(!isValid)
{ {
out.printerr("Invalid address:%x\n",memdata.addr); out.printerr("Invalid address: %p\n",memdata.addr);
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }

@ -51,7 +51,7 @@ command_result df_notes (color_ostream &con, vector <string> & parameters)
{ {
t_note* note = (*note_list)[i]; t_note* note = (*note_list)[i];
con.print("Note %x at: %d/%d/%d\n",note, note->x, note->y, note->z); con.print("Note %p at: %d/%d/%d\n",note, note->x, note->y, note->z);
con.print("Note id: %d\n", note->id); con.print("Note id: %d\n", note->id);
con.print("Note symbol: '%c'\n", note->symbol); con.print("Note symbol: '%c'\n", note->symbol);

@ -135,8 +135,8 @@ static void printVec(color_ostream &con, const char* msg, t_vecTriplet *vec,
uintptr_t length = (intptr_t)vec->end - (intptr_t)vec->start; uintptr_t length = (intptr_t)vec->end - (intptr_t)vec->start;
uintptr_t offset = pos - start; uintptr_t offset = pos - start;
con.print("%8s offset %06p, addr %010p, start %010p, length %u", con.print("%8s offset 0x%06zx, addr 0x%01zx, start 0x%01zx, length %zi",
msg, offset, pos, vec->start, length); msg, offset, pos, intptr_t(vec->start), length);
if (length >= 4 && length % 4 == 0) if (length >= 4 && length % 4 == 0)
{ {
void *ptr = vec->start; void *ptr = vec->start;
@ -200,10 +200,10 @@ command_result df_vectors (color_ostream &con, vector <string> & parameters)
// Found the range containing the start // Found the range containing the start
if (!range.isInRange((void *)end)) if (!range.isInRange((void *)end))
{ {
con.print("Scanning %u bytes would read past end of memory " con.print("Scanning %zi bytes would read past end of memory "
"range.\n", bytes); "range.\n", bytes);
size_t diff = end - (intptr_t)range.end; size_t diff = end - (intptr_t)range.end;
con.print("Cutting bytes down by %u.\n", diff); con.print("Cutting bytes down by %zi.\n", diff);
end = (uintptr_t) range.end; end = (uintptr_t) range.end;
} }

@ -7,6 +7,7 @@
#include <map> #include <map>
#include <vector> #include <vector>
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/World.h" #include "modules/World.h"
#include "df/enabler.h" #include "df/enabler.h"
@ -52,7 +53,7 @@ command_result df_zoom (color_ostream &out, std::vector <std::string> & paramete
return CR_FAILURE; return CR_FAILURE;
} }
df::zoom_commands cmd = zcmap[parameters[0]]; df::zoom_commands cmd = zcmap[parameters[0]];
enabler->zoom_display(cmd); Screen::zoom(cmd);
if (cmd == df::zoom_commands::zoom_fullscreen) if (cmd == df::zoom_commands::zoom_fullscreen)
enabler->fullscreen = !enabler->fullscreen; enabler->fullscreen = !enabler->fullscreen;
return CR_OK; return CR_OK;

@ -260,7 +260,7 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_map<df:
df::world_site::find(df::global::ui->site_id), 0); df::world_site::find(df::global::ui->site_id), 0);
if ( out_items.size() != 1 ) { if ( out_items.size() != 1 ) {
out.print("%s, %d: wrong size: %d.\n", __FILE__, __LINE__, out_items.size()); out.print("%s, %d: wrong size: %zu.\n", __FILE__, __LINE__, out_items.size());
return -1; return -1;
} }
out_items[0]->moveToGround(firstInvader->pos.x, firstInvader->pos.y, firstInvader->pos.z); out_items[0]->moveToGround(firstInvader->pos.x, firstInvader->pos.y, firstInvader->pos.z);

@ -62,6 +62,7 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <cinttypes>
using namespace std; using namespace std;
@ -301,7 +302,7 @@ command_result diggingInvadersCommand(color_ostream& out, std::vector<std::strin
DigAbilities& abilities = digAbilities[raceString]; DigAbilities& abilities = digAbilities[raceString];
df::coord bob = Gui::getCursorPos(); df::coord bob = Gui::getCursorPos();
out.print("(%d,%d,%d), (%d,%d,%d): cost = %lld\n", lastDebugEdgeCostPoint.x, lastDebugEdgeCostPoint.y, lastDebugEdgeCostPoint.z, bob.x, bob.y, bob.z, getEdgeCost(out, lastDebugEdgeCostPoint, bob, abilities)); out.print("(%d,%d,%d), (%d,%d,%d): cost = %" PRId64 "\n", lastDebugEdgeCostPoint.x, lastDebugEdgeCostPoint.y, lastDebugEdgeCostPoint.z, bob.x, bob.y, bob.z, getEdgeCost(out, lastDebugEdgeCostPoint, bob, abilities));
lastDebugEdgeCostPoint = bob; lastDebugEdgeCostPoint = bob;
a++; a++;
} else if ( parameters[a] == "now" ) { } else if ( parameters[a] == "now" ) {
@ -389,7 +390,7 @@ void findAndAssignInvasionJob(color_ostream& out, void* tickTime) {
//find all locals and invaders //find all locals and invaders
for ( size_t a = 0; a < world->units.all.size(); a++ ) { for ( size_t a = 0; a < world->units.all.size(); a++ ) {
df::unit* unit = world->units.all[a]; df::unit* unit = world->units.all[a];
if ( unit->flags1.bits.dead ) if ( !Units::isActive(unit) )
continue; continue;
if ( Units::isCitizen(unit) ) { if ( Units::isCitizen(unit) ) {
if ( localPts.find(unit->pos) != localPts.end() ) if ( localPts.find(unit->pos) != localPts.end() )

@ -1062,7 +1062,7 @@ public:
{ {
df::unit *selected_unit = (selected_column == 1) ? dwarf_activity_column.getFirstSelectedElem() : nullptr; df::unit *selected_unit = (selected_column == 1) ? dwarf_activity_column.getFirstSelectedElem() : nullptr;
Screen::dismiss(this); Screen::dismiss(this);
Screen::show(new ViewscreenDwarfStats(selected_unit), plugin_self); Screen::show(dts::make_unique<ViewscreenDwarfStats>(selected_unit), plugin_self);
} }
else if (input->count(interface_key::CUSTOM_SHIFT_Z)) else if (input->count(interface_key::CUSTOM_SHIFT_Z))
{ {
@ -1643,7 +1643,7 @@ public:
{ {
auto unitscr = df::allocate<df::viewscreen_unitst>(); auto unitscr = df::allocate<df::viewscreen_unitst>();
unitscr->unit = unit; unitscr->unit = unit;
Screen::show(unitscr); Screen::show(std::unique_ptr<df::viewscreen>(unitscr));
} }
} }
else if (input->count(interface_key::CUSTOM_SHIFT_Z)) else if (input->count(interface_key::CUSTOM_SHIFT_Z))
@ -1737,7 +1737,7 @@ private:
static void open_stats_screen() static void open_stats_screen()
{ {
Screen::show(new ViewscreenFortStats(), plugin_self); Screen::show(dts::make_unique<ViewscreenFortStats>(), plugin_self);
} }
static void add_work_history(df::unit *unit, activity_type type) static void add_work_history(df::unit *unit, activity_type type)
@ -1977,12 +1977,12 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
else if (cmd == 's' || cmd == 'S') else if (cmd == 's' || cmd == 'S')
{ {
if(Maps::IsValid()) if(Maps::IsValid())
Screen::show(new ViewscreenFortStats(), plugin_self); Screen::show(dts::make_unique<ViewscreenFortStats>(), plugin_self);
} }
else if (cmd == 'p' || cmd == 'P') else if (cmd == 'p' || cmd == 'P')
{ {
if(Maps::IsValid()) if(Maps::IsValid())
Screen::show(new ViewscreenPreferences(), plugin_self); Screen::show(dts::make_unique<ViewscreenPreferences>(), plugin_self);
} }
else if (cmd == 'r' || cmd == 'R') else if (cmd == 'r' || cmd == 'R')
{ {

@ -383,7 +383,7 @@ void AnimalHospital::processPatients(color_ostream &out) {
} }
// Check to make sure the unit hasn't expired before assigning a job, or if they've been healed // Check to make sure the unit hasn't expired before assigning a job, or if they've been healed
if (!real_unit || real_unit->flags1.bits.dead || !real_unit->health->flags.bits.needs_healthcare) { if (!real_unit || !Units::isActive(real_unit) || !real_unit->health->flags.bits.needs_healthcare) {
// discharge the patient from the hospital // discharge the patient from the hospital
this->dischargePatient(*patient, out); this->dischargePatient(*patient, out);
return; return;
@ -582,7 +582,7 @@ void tickHandler(color_ostream& out, void* data) {
/* Now add it to the scratch AHZ */ /* Now add it to the scratch AHZ */
for (vector<df::building*>::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) { for (vector<df::building*>::iterator current_hospital = to_be_added.begin(); current_hospital != to_be_added.end(); current_hospital++) {
// Add it to the vector // Add it to the vector
out.print("Adding new hospital #id at x1 %d y1: %d z: %d\n", out.print("Adding new hospital #id: %d at x1 %d y1: %d z: %d\n",
(*current_hospital)->id, (*current_hospital)->id,
(*current_hospital)->x1, (*current_hospital)->x1,
(*current_hospital)->y1, (*current_hospital)->y1,
@ -624,7 +624,7 @@ processUnits:
df::unit* unit = units[a]; df::unit* unit = units[a];
/* As hilarious as it would be, lets not treat FB :) */ /* As hilarious as it would be, lets not treat FB :) */
if ( unit->flags1.bits.dead || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) { if ( !Units::isActive(unit) || unit->flags1.bits.active_invader || unit->flags2.bits.underworld || unit->flags2.bits.visitor_uninvited || unit->flags2.bits.visitor ) {
continue; continue;
} }

@ -58,9 +58,15 @@ namespace embark_assist {
int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used
int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used
uint8_t biome_count; uint8_t biome_count;
bool evil_weather[10]; bool blood_rain[10];
bool evil_weather_possible; bool blood_rain_possible;
bool evil_weather_full; bool blood_rain_full;
bool permanent_syndrome_rain[10];
bool permanent_syndrome_rain_possible;
bool permanent_syndrome_rain_full;
bool temporary_syndrome_rain[10];
bool temporary_syndrome_rain_possible;
bool temporary_syndrome_rain_full;
bool reanimating[10]; bool reanimating[10];
bool reanimating_possible; bool reanimating_possible;
bool reanimating_full; bool reanimating_full;
@ -204,6 +210,25 @@ namespace embark_assist {
Very_Deep Very_Deep
}; };
enum class syndrome_rain_ranges : int8_t {
NA = -1,
Any,
Permanent,
Temporary,
Not_Permanent,
None
};
enum class reanimation_ranges : int8_t {
NA = -1,
Both,
Any,
Thralling,
Reanimation,
Not_Thralling,
None
};
/* // Future possible enhancement /* // Future possible enhancement
enum class freezing_ranges : int8_t { enum class freezing_ranges : int8_t {
NA = -1, NA = -1,
@ -232,9 +257,9 @@ namespace embark_assist {
all_present_ranges soil_min_everywhere; all_present_ranges soil_min_everywhere;
soil_ranges soil_max; soil_ranges soil_max;
/*freezing_ranges freezing;*/ /*freezing_ranges freezing;*/
yes_no_ranges evil_weather; // Will probably blow up with the magic release arcs... yes_no_ranges blood_rain; // Will probably blow up with the magic release arcs...
yes_no_ranges reanimation; syndrome_rain_ranges syndrome_rain;
yes_no_ranges thralling; reanimation_ranges reanimation;
int8_t spire_count_min; // N/A(-1), 0-9 int8_t spire_count_min; // N/A(-1), 0-9
int8_t spire_count_max; // N/A(-1), 0-9 int8_t spire_count_max; // N/A(-1), 0-9
magma_ranges magma_min; magma_ranges magma_min;

@ -259,7 +259,9 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
for (uint8_t l = 1; l < 10; l++) { for (uint8_t l = 1; l < 10; l++) {
embark_assist::main::state->survey_results[i][k].biome_index[l] = -1; embark_assist::main::state->survey_results[i][k].biome_index[l] = -1;
embark_assist::main::state->survey_results[i][k].biome[l] = -1; embark_assist::main::state->survey_results[i][k].biome[l] = -1;
embark_assist::main::state->survey_results[i][k].evil_weather[l] = false; embark_assist::main::state->survey_results[i][k].blood_rain[l] = false;
embark_assist::main::state->survey_results[i][k].permanent_syndrome_rain[l] = false;
embark_assist::main::state->survey_results[i][k].temporary_syndrome_rain[l] = false;
embark_assist::main::state->survey_results[i][k].reanimating[l] = false; embark_assist::main::state->survey_results[i][k].reanimating[l] = false;
embark_assist::main::state->survey_results[i][k].thralling[l] = false; embark_assist::main::state->survey_results[i][k].thralling[l] = false;
} }

@ -46,9 +46,9 @@ namespace embark_assist {
soil_min, soil_min,
soil_min_everywhere, soil_min_everywhere,
soil_max, soil_max,
evil_weather, blood_rain,
syndrome_rain,
reanimation, reanimation,
thralling,
spire_count_min, spire_count_min,
spire_count_max, spire_count_max,
magma_min, magma_min,
@ -451,9 +451,7 @@ namespace embark_assist {
case fields::waterfall: case fields::waterfall:
case fields::flat: case fields::flat:
case fields::evil_weather: case fields::blood_rain:
case fields::reanimation:
case fields::thralling:
{ {
embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA; embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA;
while (true) { while (true) {
@ -576,6 +574,91 @@ namespace embark_assist {
break; break;
case fields::syndrome_rain:
{
embark_assist::defs::syndrome_rain_ranges k = embark_assist::defs::syndrome_rain_ranges::NA;
while (true) {
switch (k) {
case embark_assist::defs::syndrome_rain_ranges::NA:
element->list.push_back({ "N/A", static_cast<int8_t>(k) });
break;
case embark_assist::defs::syndrome_rain_ranges::Any:
element->list.push_back({ "Any Syndrome", static_cast<int8_t>(k) });
break;
case embark_assist::defs::syndrome_rain_ranges::Permanent:
element->list.push_back({ "Permanent Syndrome", static_cast<int8_t>(k) });
break;
case embark_assist::defs::syndrome_rain_ranges::Temporary:
element->list.push_back({ "Temporary Syndrome", static_cast<int8_t>(k) });
break;
case embark_assist::defs::syndrome_rain_ranges::Not_Permanent:
element->list.push_back({ "Not Permanent Syndrome", static_cast<int8_t>(k) });
break;
case embark_assist::defs::syndrome_rain_ranges::None:
element->list.push_back({ "No Syndrome", static_cast<int8_t>(k) });
break;
}
if (k == embark_assist::defs::syndrome_rain_ranges::None) {
break;
}
k = static_cast <embark_assist::defs::syndrome_rain_ranges>(static_cast<int8_t>(k) + 1);
}
}
break;
case fields::reanimation:
{
embark_assist::defs::reanimation_ranges k = embark_assist::defs::reanimation_ranges::NA;
while (true) {
switch (k) {
case embark_assist::defs::reanimation_ranges::NA:
element->list.push_back({ "N/A", static_cast<int8_t>(k) });
break;
case embark_assist::defs::reanimation_ranges::Both:
element->list.push_back({ "Reanimation & Thralling", static_cast<int8_t>(k) });
break;
case embark_assist::defs::reanimation_ranges::Any:
element->list.push_back({ "Reanimation or Thralling", static_cast<int8_t>(k) });
break;
case embark_assist::defs::reanimation_ranges::Thralling:
element->list.push_back({ "Thralling", static_cast<int8_t>(k) });
break;
case embark_assist::defs::reanimation_ranges::Reanimation:
element->list.push_back({ "Reanimation", static_cast<int8_t>(k) });
break;
case embark_assist::defs::reanimation_ranges::Not_Thralling:
element->list.push_back({ "Not Thralling", static_cast<int8_t>(k) });
break;
case embark_assist::defs::reanimation_ranges::None:
element->list.push_back({ "None", static_cast<int8_t>(k) });
break;
}
if (k == embark_assist::defs::reanimation_ranges::None) {
break;
}
k = static_cast <embark_assist::defs::reanimation_ranges>(static_cast<int8_t>(k) + 1);
}
}
break;
case fields::spire_count_min: case fields::spire_count_min:
case fields::spire_count_max: case fields::spire_count_max:
for (int16_t k = -1; k <= 9; k++) { for (int16_t k = -1; k <= 9; k++) {
@ -834,16 +917,16 @@ namespace embark_assist {
state->finder_list.push_back({ "Min Soil Everywhere", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Min Soil Everywhere", static_cast<int8_t>(i) });
break; break;
case fields::evil_weather: case fields::blood_rain:
state->finder_list.push_back({ "Evil Weather", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Blood Rain", static_cast<int8_t>(i) });
break; break;
case fields::reanimation: case fields::syndrome_rain:
state->finder_list.push_back({ "Reanimation", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Syndrome Rain", static_cast<int8_t>(i) });
break; break;
case fields::thralling: case fields::reanimation:
state->finder_list.push_back({ "Thralling", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Reanimation", static_cast<int8_t>(i) });
break; break;
case fields::clay: case fields::clay:
@ -1058,19 +1141,19 @@ namespace embark_assist {
static_cast<embark_assist::defs::all_present_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value); static_cast<embark_assist::defs::all_present_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break; break;
case fields::evil_weather: case fields::blood_rain:
finder.evil_weather = finder.blood_rain =
static_cast<embark_assist::defs::yes_no_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value); static_cast<embark_assist::defs::yes_no_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break; break;
case fields::reanimation: case fields::syndrome_rain:
finder.reanimation = finder.syndrome_rain =
static_cast<embark_assist::defs::yes_no_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value); static_cast<embark_assist::defs::syndrome_rain_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break; break;
case fields::thralling: case fields::reanimation:
finder.thralling = finder.reanimation =
static_cast<embark_assist::defs::yes_no_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value); static_cast<embark_assist::defs::reanimation_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break; break;
case fields::clay: case fields::clay:
@ -1380,7 +1463,7 @@ void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::
if (!embark_assist::finder_ui::state) { // First call. Have to do the setup if (!embark_assist::finder_ui::state) { // First call. Have to do the setup
embark_assist::finder_ui::ui_setup(find_callback, max_inorganic); embark_assist::finder_ui::ui_setup(find_callback, max_inorganic);
} }
Screen::show(new ViewscreenFindUi(), plugin_self); Screen::show(dts::make_unique<ViewscreenFindUi>(), plugin_self);
} }
//=============================================================================== //===============================================================================

@ -209,6 +209,11 @@ namespace embark_assist{
help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil"); help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil");
help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and"); help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and");
help_text.push_back("and Present."); help_text.push_back("and Present.");
help_text.push_back("Syndrome Rain allows you to search for Permanent and Temporary syndromes,");
help_text.push_back("where Permanent allows for Temporary ones as well, but not the reverse, as");
help_text.push_back("Not Permanent matches everything except Permanent syndromes.");
help_text.push_back("Reanimation packages thralling and reanimation into a single search");
help_text.push_back("criterion. Not Tralling means nothing and just reanimation is matched.");
help_text.push_back("The parameters for biomes, regions, etc. all require that the required"); help_text.push_back("The parameters for biomes, regions, etc. all require that the required");
help_text.push_back("feature is Present in the embark, and entering the same value multiple"); help_text.push_back("feature is Present in the embark, and entering the same value multiple");
help_text.push_back("times does nothing (the first match ticks all requirements off). It can be"); help_text.push_back("times does nothing (the first match ticks all requirements off). It can be");
@ -254,9 +259,9 @@ namespace embark_assist{
help_text.push_back(" emulate the sizing logic exactly."); help_text.push_back(" emulate the sizing logic exactly.");
help_text.push_back("- There's currently a DF bug (#0010267) that causes adamantine spires"); help_text.push_back("- There's currently a DF bug (#0010267) that causes adamantine spires");
help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); help_text.push_back(" reaching caverns that have been removed at world gen to fail to be");
help_text.push_back(" generated. It's likely this bug also affects magma pools."); help_text.push_back(" generated at all. It's likely this bug also affects magma pools.");
help_text.push_back(" This plugin does not address this but scripts can correct it."); help_text.push_back(" This plugin does not address this but scripts can correct it.");
help_text.push_back("Version 0.3 2018-02-26"); help_text.push_back("Version 0.4 2018-06-21");
break; break;
} }
@ -322,5 +327,5 @@ namespace embark_assist{
//=============================================================================== //===============================================================================
void embark_assist::help_ui::init(DFHack::Plugin *plugin_self) { void embark_assist::help_ui::init(DFHack::Plugin *plugin_self) {
Screen::show(new embark_assist::help_ui::ViewscreenHelpUi(), plugin_self); Screen::show(dts::make_unique<embark_assist::help_ui::ViewscreenHelpUi>(), plugin_self);
} }

@ -48,7 +48,9 @@ namespace embark_assist {
bool flux_found = false; bool flux_found = false;
uint8_t max_soil = 0; uint8_t max_soil = 0;
bool uneven = false; bool uneven = false;
bool evil_weather_found = false; bool blood_rain_found = false;
bool permanent_syndrome_rain_found = false;
bool temporary_syndrome_rain_found = false;
bool reanimation_found = false; bool reanimation_found = false;
bool thralling_found = false; bool thralling_found = false;
uint8_t spire_count = 0; uint8_t spire_count = 0;
@ -183,21 +185,39 @@ namespace embark_assist {
if (finder->soil_max != embark_assist::defs::soil_ranges::NA && if (finder->soil_max != embark_assist::defs::soil_ranges::NA &&
mlt->at(i).at(k).soil_depth > static_cast<uint16_t>(finder->soil_max)) return false; mlt->at(i).at(k).soil_depth > static_cast<uint16_t>(finder->soil_max)) return false;
// Evil Weather // Blood Rain
if (survey_results->at(x).at(y).evil_weather[mlt->at(i).at(k).biome_offset]) { if (survey_results->at(x).at(y).blood_rain[mlt->at(i).at(k).biome_offset]) {
if (finder->evil_weather == embark_assist::defs::yes_no_ranges::No) return false; if (finder->blood_rain == embark_assist::defs::yes_no_ranges::No) return false;
evil_weather_found = true; blood_rain_found = true;
}
// Syndrome Rain, Permanent
if (survey_results->at(x).at(y).permanent_syndrome_rain[mlt->at(i).at(k).biome_offset]) {
if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Temporary ||
finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Not_Permanent ||
finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::None) return false;
permanent_syndrome_rain_found = true;
}
// Syndrome Rain, Temporary
if (survey_results->at(x).at(y).temporary_syndrome_rain[mlt->at(i).at(k).biome_offset]) {
if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Permanent ||
finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::None) return false;
temporary_syndrome_rain_found = true;
} }
// Reanmation // Reanmation
if (survey_results->at(x).at(y).reanimating[mlt->at(i).at(k).biome_offset]) { if (survey_results->at(x).at(y).reanimating[mlt->at(i).at(k).biome_offset]) {
if (finder->reanimation == embark_assist::defs::yes_no_ranges::No) return false; if (finder->reanimation == embark_assist::defs::reanimation_ranges::Thralling ||
finder->reanimation == embark_assist::defs::reanimation_ranges::None) return false;
reanimation_found = true; reanimation_found = true;
} }
// Thralling // Thralling
if (survey_results->at(x).at(y).thralling[mlt->at(i).at(k).biome_offset]) { if (survey_results->at(x).at(y).thralling[mlt->at(i).at(k).biome_offset]) {
if (finder->thralling == embark_assist::defs::yes_no_ranges::No) return false; if (finder->reanimation == embark_assist::defs::reanimation_ranges::Reanimation ||
finder->reanimation == embark_assist::defs::reanimation_ranges::Not_Thralling ||
finder->reanimation == embark_assist::defs::reanimation_ranges::None) return false;
thralling_found = true; thralling_found = true;
} }
@ -303,14 +323,19 @@ namespace embark_assist {
finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present && finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present &&
max_soil < static_cast<uint8_t>(finder->soil_min)) return false; max_soil < static_cast<uint8_t>(finder->soil_min)) return false;
// Evil Weather // Blood Rain
if (finder->evil_weather == embark_assist::defs::yes_no_ranges::Yes && !evil_weather_found) return false; if (finder->blood_rain == embark_assist::defs::yes_no_ranges::Yes && !blood_rain_found) return false;
// Reanimation // Syndrome Rain
if (finder->reanimation == embark_assist::defs::yes_no_ranges::Yes && !reanimation_found) return false; if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Any && !permanent_syndrome_rain_found && !temporary_syndrome_rain_found) return false;
if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Permanent && !permanent_syndrome_rain_found) return false;
if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Temporary && !temporary_syndrome_rain_found) return false;
// Thralling // Reanimation
if (finder->thralling == embark_assist::defs::yes_no_ranges::Yes && !thralling_found) return false; if (finder->reanimation == embark_assist::defs::reanimation_ranges::Both && !(reanimation_found && thralling_found)) return false;
if (finder->reanimation == embark_assist::defs::reanimation_ranges::Any && !reanimation_found && !thralling_found) return false;
if (finder->reanimation == embark_assist::defs::reanimation_ranges::Thralling && !thralling_found) return false;
if (finder->reanimation == embark_assist::defs::reanimation_ranges::Reanimation && !reanimation_found) return false;
// Spires // Spires
if (finder->spire_count_min != -1 && finder->spire_count_min > spire_count) return false; if (finder->spire_count_min != -1 && finder->spire_count_min > spire_count) return false;
@ -597,46 +622,74 @@ namespace embark_assist {
break; break;
} }
// Evil Weather // Blood Rain
switch (finder->evil_weather) { switch (finder->blood_rain) {
case embark_assist::defs::yes_no_ranges::NA: case embark_assist::defs::yes_no_ranges::NA:
break; // No restriction break; // No restriction
case embark_assist::defs::yes_no_ranges::Yes: case embark_assist::defs::yes_no_ranges::Yes:
if (!tile->evil_weather_possible) return false; if (!tile->blood_rain_possible) return false;
break; break;
case embark_assist::defs::yes_no_ranges::No: case embark_assist::defs::yes_no_ranges::No:
if (tile->evil_weather_full) return false; if (tile->blood_rain_full) return false;
break; break;
} }
// Reanimating // Syndrome Rain
switch (finder->reanimation) { switch (finder->syndrome_rain) {
case embark_assist::defs::yes_no_ranges::NA: case embark_assist::defs::syndrome_rain_ranges::NA:
break; // No restriction break; // No restriction
case embark_assist::defs::yes_no_ranges::Yes: case embark_assist::defs::syndrome_rain_ranges::Any:
if (!tile->reanimating_possible) return false; if (!tile->permanent_syndrome_rain_possible && !tile->temporary_syndrome_rain_possible) return false;
break; break;
case embark_assist::defs::yes_no_ranges::No: case embark_assist::defs::syndrome_rain_ranges::Permanent:
if (tile->reanimating_full) return false; if (!tile->permanent_syndrome_rain_possible) return false;
break;
case embark_assist::defs::syndrome_rain_ranges::Temporary:
if (!tile->temporary_syndrome_rain_possible) return false;
break;
case embark_assist::defs::syndrome_rain_ranges::Not_Permanent:
if (tile->permanent_syndrome_rain_full) return false;
break;
case embark_assist::defs::syndrome_rain_ranges::None:
if (tile->permanent_syndrome_rain_full || tile->temporary_syndrome_rain_full) return false;
break; break;
} }
// Thralling // Reanimating
switch (finder->thralling) { switch (finder->reanimation) {
case embark_assist::defs::yes_no_ranges::NA: case embark_assist::defs::reanimation_ranges::NA:
break; // No restriction break; // No restriction
case embark_assist::defs::yes_no_ranges::Yes: case embark_assist::defs::reanimation_ranges::Both:
if (!tile->reanimating_possible || !tile->thralling_possible) return false;
break;
case embark_assist::defs::reanimation_ranges::Any:
if (!tile->reanimating_possible && !tile->thralling_possible) return false;
break;
case embark_assist::defs::reanimation_ranges::Thralling:
if (!tile->thralling_possible) return false; if (!tile->thralling_possible) return false;
break; break;
case embark_assist::defs::yes_no_ranges::No: case embark_assist::defs::reanimation_ranges::Reanimation:
if (!tile->reanimating_possible) return false;
break;
case embark_assist::defs::reanimation_ranges::Not_Thralling:
if (tile->thralling_full) return false; if (tile->thralling_full) return false;
break; break;
case embark_assist::defs::reanimation_ranges::None:
if (tile->reanimating_full || tile->thralling_full) return false;
break;
} }
// Spire Count Min/Max // Spire Count Min/Max
@ -950,46 +1003,74 @@ namespace embark_assist {
// Soil Max // Soil Max
// Can't say anything as the preliminary data isn't reliable // Can't say anything as the preliminary data isn't reliable
// Evil Weather // Blood Rain
switch (finder->evil_weather) { switch (finder->blood_rain) {
case embark_assist::defs::yes_no_ranges::NA: case embark_assist::defs::yes_no_ranges::NA:
break; // No restriction break; // No restriction
case embark_assist::defs::yes_no_ranges::Yes: case embark_assist::defs::yes_no_ranges::Yes:
if (!tile->evil_weather_possible) return false; if (!tile->blood_rain_possible) return false;
break; break;
case embark_assist::defs::yes_no_ranges::No: case embark_assist::defs::yes_no_ranges::No:
if (tile->evil_weather_full) return false; if (tile->blood_rain_full) return false;
break; break;
} }
// Reanimating // Syndrome Rain
switch (finder->reanimation) { switch (finder->syndrome_rain) {
case embark_assist::defs::yes_no_ranges::NA: case embark_assist::defs::syndrome_rain_ranges::NA:
break; // No restriction break; // No restriction
case embark_assist::defs::yes_no_ranges::Yes: case embark_assist::defs::syndrome_rain_ranges::Any:
if (!tile->reanimating_possible) return false; if (!tile->permanent_syndrome_rain_possible && !tile->temporary_syndrome_rain_possible) return false;
break; break;
case embark_assist::defs::yes_no_ranges::No: case embark_assist::defs::syndrome_rain_ranges::Permanent:
if (tile->reanimating_full) return false; if (!tile->permanent_syndrome_rain_possible) return false;
break;
case embark_assist::defs::syndrome_rain_ranges::Temporary:
if (!tile->temporary_syndrome_rain_possible) return false;
break;
case embark_assist::defs::syndrome_rain_ranges::Not_Permanent:
if (tile->permanent_syndrome_rain_full) return false;
break;
case embark_assist::defs::syndrome_rain_ranges::None:
if (tile->permanent_syndrome_rain_full || tile->temporary_syndrome_rain_full) return false;
break; break;
} }
// Thralling // Reanimating
switch (finder->thralling) { switch (finder->reanimation) {
case embark_assist::defs::yes_no_ranges::NA: case embark_assist::defs::reanimation_ranges::NA:
break; // No restriction break; // No restriction
case embark_assist::defs::yes_no_ranges::Yes: case embark_assist::defs::reanimation_ranges::Both:
if (!tile->reanimating_possible || !tile->thralling_possible) return false;
break;
case embark_assist::defs::reanimation_ranges::Any:
if (!tile->reanimating_possible && !tile->thralling_possible) return false;
break;
case embark_assist::defs::reanimation_ranges::Thralling:
if (!tile->thralling_possible) return false; if (!tile->thralling_possible) return false;
break; break;
case embark_assist::defs::yes_no_ranges::No: case embark_assist::defs::reanimation_ranges::Reanimation:
if (!tile->reanimating_possible) return false;
break;
case embark_assist::defs::reanimation_ranges::Not_Thralling:
if (tile->thralling_full) return false; if (tile->thralling_full) return false;
break; break;
case embark_assist::defs::reanimation_ranges::None:
if (tile->reanimating_full || tile->thralling_full) return false;
break;
} }
// Spire Count Min/Max // Spire Count Min/Max

@ -259,12 +259,14 @@ namespace embark_assist {
//================================================================================= //=================================================================================
void survey_evil_weather(embark_assist::defs::world_tile_data *survey_results) { void survey_evil_weather(embark_assist::defs::world_tile_data *survey_results) {
// color_ostream_proxy out(Core::getInstance().getConsole());
df::world_data *world_data = world->world_data; df::world_data *world_data = world->world_data;
for (uint16_t i = 0; i < world->interaction_instances.all.size(); i++) { for (uint16_t i = 0; i < world->interaction_instances.all.size(); i++) {
auto interaction = world->raws.interactions[world->interaction_instances.all[i]->interaction_id]; auto interaction = world->raws.interactions[world->interaction_instances.all[i]->interaction_id];
uint16_t region_index = world->interaction_instances.all[i]->region_index; uint16_t region_index = world->interaction_instances.all[i]->region_index;
bool blood_rain = false;
bool permanent_syndrome_rain = false;
bool temporary_syndrome_rain = false;
bool thralling = false; bool thralling = false;
bool reanimating = false; bool reanimating = false;
@ -282,62 +284,115 @@ namespace embark_assist {
else if (interaction->targets[k]->getType() == df::interaction_target_type::MATERIAL) { else if (interaction->targets[k]->getType() == df::interaction_target_type::MATERIAL) {
df::interaction_target_materialst* material = virtual_cast<df::interaction_target_materialst>(interaction->targets[k]); df::interaction_target_materialst* material = virtual_cast<df::interaction_target_materialst>(interaction->targets[k]);
if (material && DFHack::MaterialInfo(material->mat_type, material->mat_index).isInorganic()) { if (material && DFHack::MaterialInfo(material->mat_type, material->mat_index).isInorganic()) {
for (uint16_t l = 0; l < world->raws.inorganics[material->mat_index]->material.syndrome.size(); l++) { for (const auto &syndrome : world->raws.inorganics[material->mat_index]->material.syndrome) {
for (uint16_t m = 0; m < world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce.size(); m++) { for (const auto &ce : syndrome->ce) {
if (world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce[m]->getType() == df::creature_interaction_effect_type::FLASH_TILE) { df::creature_interaction_effect_type ce_type = ce->getType();
if (ce_type == df::creature_interaction_effect_type::FLASH_TILE) {
// Using this as a proxy. There seems to be a group of 4 effects for thralls: // Using this as a proxy. There seems to be a group of 4 effects for thralls:
// display symbol, flash symbol, phys att change and one more. // display symbol, flash symbol, phys att change and one more.
thralling = true; thralling = true;
} }
else if (ce_type == df::creature_interaction_effect_type::PAIN ||
ce_type == df::creature_interaction_effect_type::SWELLING ||
ce_type == df::creature_interaction_effect_type::OOZING ||
ce_type == df::creature_interaction_effect_type::BRUISING ||
ce_type == df::creature_interaction_effect_type::BLISTERS ||
ce_type == df::creature_interaction_effect_type::NUMBNESS ||
ce_type == df::creature_interaction_effect_type::PARALYSIS ||
ce_type == df::creature_interaction_effect_type::FEVER ||
ce_type == df::creature_interaction_effect_type::BLEEDING ||
ce_type == df::creature_interaction_effect_type::COUGH_BLOOD ||
ce_type == df::creature_interaction_effect_type::VOMIT_BLOOD ||
ce_type == df::creature_interaction_effect_type::NAUSEA ||
ce_type == df::creature_interaction_effect_type::UNCONSCIOUSNESS ||
ce_type == df::creature_interaction_effect_type::NECROSIS ||
ce_type == df::creature_interaction_effect_type::IMPAIR_FUNCTION ||
ce_type == df::creature_interaction_effect_type::DROWSINESS ||
ce_type == df::creature_interaction_effect_type::DIZZINESS ||
ce_type == df::creature_interaction_effect_type::ERRATIC_BEHAVIOR) { // Doubtful if possible for region.
if (ce->end == -1) {
permanent_syndrome_rain = true;
}
else {
temporary_syndrome_rain = true;
}
}
} }
} }
} }
else { // If not inorganic it's always blood, as far as known.
blood_rain = true;
}
} }
} }
} }
for (uint16_t k = 0; k < world_data->regions[region_index]->region_coords.size(); k++) { for (uint16_t k = 0; k < world_data->regions[region_index]->region_coords.size(); k++) {
survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).evil_weather[5] = true; auto &results = survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y);
survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).reanimating[5] = reanimating; results.blood_rain[5] = blood_rain;
survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).thralling[5] = thralling; results.permanent_syndrome_rain[5] = permanent_syndrome_rain;
results.temporary_syndrome_rain[5] = temporary_syndrome_rain;
results.reanimating[5] = reanimating;
results.thralling[5] = thralling;
} }
} }
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
survey_results->at(i).at(k).evil_weather_possible = false; auto &results = survey_results->at(i).at(k);
survey_results->at(i).at(k).reanimating_possible = false; results.blood_rain_possible = false;
survey_results->at(i).at(k).thralling_possible = false; results.permanent_syndrome_rain_possible = false;
survey_results->at(i).at(k).evil_weather_full = true; results.temporary_syndrome_rain_possible = false;
survey_results->at(i).at(k).reanimating_full = true; results.reanimating_possible = false;
survey_results->at(i).at(k).thralling_full = true; results.thralling_possible = false;
results.blood_rain_full = true;
results.permanent_syndrome_rain_full = true;
results.temporary_syndrome_rain_full = true;
results.reanimating_full = true;
results.thralling_full = true;
for (uint8_t l = 1; l < 10; l++) { for (uint8_t l = 1; l < 10; l++) {
if (survey_results->at(i).at(k).biome_index[l] != -1) { if (results.biome_index[l] != -1) {
df::coord2d adjusted = apply_offset(i, k, l); df::coord2d adjusted = apply_offset(i, k, l);
survey_results->at(i).at(k).evil_weather[l] = survey_results->at(adjusted.x).at(adjusted.y).evil_weather[5]; results.blood_rain[l] = survey_results->at(adjusted.x).at(adjusted.y).blood_rain[5];
survey_results->at(i).at(k).reanimating[l] = survey_results->at(adjusted.x).at(adjusted.y).reanimating[5]; results.permanent_syndrome_rain[l] = survey_results->at(adjusted.x).at(adjusted.y).permanent_syndrome_rain[5];
survey_results->at(i).at(k).thralling[l] = survey_results->at(adjusted.x).at(adjusted.y).thralling[5]; results.temporary_syndrome_rain[l] = survey_results->at(adjusted.x).at(adjusted.y).temporary_syndrome_rain[5];
results.reanimating[l] = survey_results->at(adjusted.x).at(adjusted.y).reanimating[5];
results.thralling[l] = survey_results->at(adjusted.x).at(adjusted.y).thralling[5];
if (results.blood_rain[l]) {
results.blood_rain_possible = true;
}
else {
results.blood_rain_full = false;
}
if (survey_results->at(i).at(k).evil_weather[l]) { if (results.permanent_syndrome_rain[l]) {
survey_results->at(i).at(k).evil_weather_possible = true; results.permanent_syndrome_rain_possible = true;
} }
else { else {
survey_results->at(i).at(k).evil_weather_full = false; results.permanent_syndrome_rain_full = false;
} }
if (survey_results->at(i).at(k).reanimating[l]) { if (results.temporary_syndrome_rain[l]) {
survey_results->at(i).at(k).reanimating_possible = true; results.temporary_syndrome_rain_possible = true;
} }
else { else {
survey_results->at(i).at(k).reanimating_full = false; results.temporary_syndrome_rain_full = false;
} }
if (survey_results->at(i).at(k).thralling[l]) { if (results.reanimating[l]) {
survey_results->at(i).at(k).thralling_possible = true; results.reanimating_possible = true;
} }
else { else {
survey_results->at(i).at(k).thralling_full = false; results.reanimating_full = false;
}
if (results.thralling[l]) {
results.thralling_possible = true;
}
else {
results.thralling_full = false;
} }
} }
} }
@ -405,23 +460,24 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
uint16_t geo_index; uint16_t geo_index;
uint16_t sav_ev; uint16_t sav_ev;
uint8_t offset_count = 0; uint8_t offset_count = 0;
survey_results->at(i).at(k).surveyed = false; auto &results = survey_results->at(i).at(k);
survey_results->at(i).at(k).aquifer_count = 0; results.surveyed = false;
survey_results->at(i).at(k).clay_count = 0; results.aquifer_count = 0;
survey_results->at(i).at(k).sand_count = 0; results.clay_count = 0;
survey_results->at(i).at(k).flux_count = 0; results.sand_count = 0;
survey_results->at(i).at(k).min_region_soil = 10; results.flux_count = 0;
survey_results->at(i).at(k).max_region_soil = 0; results.min_region_soil = 10;
survey_results->at(i).at(k).waterfall = false; results.max_region_soil = 0;
survey_results->at(i).at(k).savagery_count[0] = 0; results.waterfall = false;
survey_results->at(i).at(k).savagery_count[1] = 0; results.savagery_count[0] = 0;
survey_results->at(i).at(k).savagery_count[2] = 0; results.savagery_count[1] = 0;
survey_results->at(i).at(k).evilness_count[0] = 0; results.savagery_count[2] = 0;
survey_results->at(i).at(k).evilness_count[1] = 0; results.evilness_count[0] = 0;
survey_results->at(i).at(k).evilness_count[2] = 0; results.evilness_count[1] = 0;
survey_results->at(i).at(k).metals.resize(state->max_inorganic); results.evilness_count[2] = 0;
survey_results->at(i).at(k).economics.resize(state->max_inorganic); results.metals.resize(state->max_inorganic);
survey_results->at(i).at(k).minerals.resize(state->max_inorganic); results.economics.resize(state->max_inorganic);
results.minerals.resize(state->max_inorganic);
// Evil weather and rivers are handled in later operations. Should probably be merged into one. // Evil weather and rivers are handled in later operations. Should probably be merged into one.
for (uint8_t l = 1; l < 10; l++) for (uint8_t l = 1; l < 10; l++)
@ -430,53 +486,53 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
if (adjusted.x != i || adjusted.y != k || l == 5) { if (adjusted.x != i || adjusted.y != k || l == 5) {
offset_count++; offset_count++;
survey_results->at(i).at(k).biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id; results.biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id;
survey_results->at(i).at(k).biome[l] = get_biome_type(adjusted.x, adjusted.y, k); results.biome[l] = get_biome_type(adjusted.x, adjusted.y, k);
geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index; geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index;
if (!geo_summary->at(geo_index).aquifer_absent) survey_results->at(i).at(k).aquifer_count++; if (!geo_summary->at(geo_index).aquifer_absent) results.aquifer_count++;
if (!geo_summary->at(geo_index).clay_absent) survey_results->at(i).at(k).clay_count++; if (!geo_summary->at(geo_index).clay_absent) results.clay_count++;
if (!geo_summary->at(geo_index).sand_absent) survey_results->at(i).at(k).sand_count++; if (!geo_summary->at(geo_index).sand_absent) results.sand_count++;
if (!geo_summary->at(geo_index).flux_absent) survey_results->at(i).at(k).flux_count++; if (!geo_summary->at(geo_index).flux_absent) results.flux_count++;
if (geo_summary->at(geo_index).soil_size < survey_results->at(i).at(k).min_region_soil) if (geo_summary->at(geo_index).soil_size < results.min_region_soil)
survey_results->at(i).at(k).min_region_soil = geo_summary->at(geo_index).soil_size; results.min_region_soil = geo_summary->at(geo_index).soil_size;
if (geo_summary->at(geo_index).soil_size > survey_results->at(i).at(k).max_region_soil) if (geo_summary->at(geo_index).soil_size > results.max_region_soil)
survey_results->at(i).at(k).max_region_soil = geo_summary->at(geo_index).soil_size; results.max_region_soil = geo_summary->at(geo_index).soil_size;
sav_ev = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; sav_ev = world_data->region_map[adjusted.x][adjusted.y].savagery / 33;
if (sav_ev == 3) sav_ev = 2; if (sav_ev == 3) sav_ev = 2;
survey_results->at(i).at(k).savagery_count[sav_ev]++; results.savagery_count[sav_ev]++;
sav_ev = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; sav_ev = world_data->region_map[adjusted.x][adjusted.y].evilness / 33;
if (sav_ev == 3) sav_ev = 2; if (sav_ev == 3) sav_ev = 2;
survey_results->at(i).at(k).evilness_count[sav_ev]++; results.evilness_count[sav_ev]++;
for (uint16_t m = 0; m < state->max_inorganic; m++) { for (uint16_t m = 0; m < state->max_inorganic; m++) {
if (geo_summary->at(geo_index).possible_metals[m]) survey_results->at(i).at(k).metals[m] = true; if (geo_summary->at(geo_index).possible_metals[m]) results.metals[m] = true;
if (geo_summary->at(geo_index).possible_economics[m]) survey_results->at(i).at(k).economics[m] = true; if (geo_summary->at(geo_index).possible_economics[m]) results.economics[m] = true;
if (geo_summary->at(geo_index).possible_minerals[m]) survey_results->at(i).at(k).minerals[m] = true; if (geo_summary->at(geo_index).possible_minerals[m]) results.minerals[m] = true;
} }
} }
else { else {
survey_results->at(i).at(k).biome_index[l] = -1; results.biome_index[l] = -1;
survey_results->at(i).at(k).biome[l] = -1; results.biome[l] = -1;
} }
} }
survey_results->at(i).at(k).biome_count = 0; results.biome_count = 0;
for (uint8_t l = 1; l < 10; l++) { for (uint8_t l = 1; l < 10; l++) {
if (survey_results->at(i).at(k).biome[l] != -1) survey_results->at(i).at(k).biome_count++; if (results.biome[l] != -1) results.biome_count++;
} }
if (survey_results->at(i).at(k).aquifer_count == offset_count) survey_results->at(i).at(k).aquifer_count = 256; if (results.aquifer_count == offset_count) results.aquifer_count = 256;
if (survey_results->at(i).at(k).clay_count == offset_count) survey_results->at(i).at(k).clay_count = 256; if (results.clay_count == offset_count) results.clay_count = 256;
if (survey_results->at(i).at(k).sand_count == offset_count) survey_results->at(i).at(k).sand_count = 256; if (results.sand_count == offset_count) results.sand_count = 256;
if (survey_results->at(i).at(k).flux_count == offset_count) survey_results->at(i).at(k).flux_count = 256; if (results.flux_count == offset_count) results.flux_count = 256;
for (uint8_t l = 0; l < 3; l++) { for (uint8_t l = 0; l < 3; l++) {
if (survey_results->at(i).at(k).savagery_count[l] == offset_count) survey_results->at(i).at(k).savagery_count[l] = 256; if (results.savagery_count[l] == offset_count) results.savagery_count[l] = 256;
if (survey_results->at(i).at(k).evilness_count[l] == offset_count) survey_results->at(i).at(k).evilness_count[l] = 256; if (results.evilness_count[l] == offset_count) results.evilness_count[l] = 256;
} }
} }
} }
@ -543,7 +599,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
else else
{ {
mlt->at(i).at(k).biome_offset = 5; mlt->at(i).at(k).biome_offset = 5;
}; }
survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset] = survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset] =
world_data->region_map[adjusted.x][adjusted.y].region_id; world_data->region_map[adjusted.x][adjusted.y].region_id;

@ -696,7 +696,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
void display_settings() void display_settings()
{ {
Screen::show(new embark_tools_settings, plugin_self); Screen::show(dts::make_unique<embark_tools_settings>(), plugin_self);
} }
inline bool is_valid_page() inline bool is_valid_page()

@ -211,7 +211,7 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
DFhackCExport command_result plugin_onupdate (color_ostream &out) DFhackCExport command_result plugin_onupdate (color_ostream &out)
{ {
if (is_enabled && World::isFortressMode()) if (is_enabled && World::isFortressMode() && Maps::IsValid())
{ {
if (world->frame_counter % run_interval == 0 && !World::ReadPauseState()) if (world->frame_counter % run_interval == 0 && !World::ReadPauseState())
{ {

@ -156,7 +156,7 @@ command_result follow (color_ostream &out, std::vector <std::string> & parameter
ss << "Unpause to begin following " << world->raws.creatures.all[followedUnit->race]->name[0]; ss << "Unpause to begin following " << world->raws.creatures.all[followedUnit->race]->name[0];
if (followedUnit->name.has_name) ss << " " << followedUnit->name.first_name; if (followedUnit->name.has_name) ss << " " << followedUnit->name.first_name;
ss << ". Simply manually move the view to break the following.\n"; ss << ". Simply manually move the view to break the following.\n";
out.print(ss.str().c_str()); out.print("%s", ss.str().c_str());
} }
else followedUnit = 0; else followedUnit = 0;
is_enabled = (followedUnit != NULL); is_enabled = (followedUnit != NULL);

@ -298,7 +298,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
else else
{ {
// The specified body part has not been found, and we've reached the end of the list. Report failure. // The specified body part has not been found, and we've reached the end of the list. Report failure.
if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); } if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n",targetBodyPart->token.c_str()); }
return false; return false;
} }

@ -270,7 +270,7 @@ command_result fortplan(color_ostream &out, vector<string> & params) {
checkCode = layout[checkY][checkX]; checkCode = layout[checkY][checkX];
} }
con.print(" - Code at (%d,%d) is '%s': ",checkX,checkY,checkCode.c_str()); con.print(" - Code at (%zu,%zu) is '%s': ",checkX,checkY,checkCode.c_str());
auto checkIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(checkCode.c_str())); auto checkIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(checkCode.c_str()));
//if (checkIndex == buildings.end()) { //if (checkIndex == buildings.end()) {
// con.print("this is not a valid code, so we keep going.\n"); // con.print("this is not a valid code, so we keep going.\n");
@ -331,16 +331,16 @@ command_result fortplan(color_ostream &out, vector<string> & params) {
offsetCursor.y -= yOffset; offsetCursor.y -= yOffset;
DFHack::Gui::setCursorCoords(offsetCursor.x, offsetCursor.y, offsetCursor.z); DFHack::Gui::setCursorCoords(offsetCursor.x, offsetCursor.y, offsetCursor.z);
if (!buildingInfo.allocate()) { if (!buildingInfo.allocate()) {
con.print("*** There was an error placing building with code '%s' centered at (%d,%d).\n",curCode.c_str(),x,y); con.print("*** There was an error placing building with code '%s' centered at (%zu,%zu).\n",curCode.c_str(),x,y);
} }
DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z);
} else if (block) { } else if (block) {
//con.print("Placing a building with code '%s' with corner at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); //con.print("Placing a building with code '%s' with corner at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight);
if (!buildingInfo.allocate()) { if (!buildingInfo.allocate()) {
con.print("*** There was an error placing building with code '%s' with corner at (%d,%d).\n",curCode.c_str(),x,y); con.print("*** There was an error placing building with code '%s' with corner at (%zu,%zu).\n",curCode.c_str(),x,y);
} }
} else { } else {
con.print("*** Found a code '%s' at (%d,%d) for a building with default size %dx%d with an invalid size designation.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); con.print("*** Found a code '%s' at (%zu,%zu) for a building with default size %dx%d with an invalid size designation.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight);
} }
} else { } else {
//buildingSize = findBuildingExtent(layout, x, y, -1, -1, out); //buildingSize = findBuildingExtent(layout, x, y, -1, -1, out);
@ -350,7 +350,7 @@ command_result fortplan(color_ostream &out, vector<string> & params) {
} else { } else {
//con.print("Building a(n) %s.\n",buildingInfo.name.c_str()); //con.print("Building a(n) %s.\n",buildingInfo.name.c_str());
if (!buildingInfo.allocate()) { if (!buildingInfo.allocate()) {
con.print("*** There was an error placing the %s at (%d,%d).\n",buildingInfo.name.c_str(),x,y); con.print("*** There was an error placing the %s at (%zu,%zu).\n",buildingInfo.name.c_str(),x,y);
} }
} }
} }

@ -199,11 +199,10 @@ command_result list_creatures(color_ostream &out, std::vector <std::string> & pa
auto creatureRaw = world->raws.creatures.all[i]; auto creatureRaw = world->raws.creatures.all[i];
if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED)) if (!creatureRaw->flags.is_set(df::enums::creature_raw_flags::GENERATED))
continue; continue;
out.print(creatureRaw->creature_id.c_str()); out.print("%s",creatureRaw->creature_id.c_str());
if (detailed) if (detailed)
{ {
out.print("\t"); out.print("\t%s",creatureRaw->caste[0]->description.c_str());
out.print(creatureRaw->caste[0]->description.c_str());
} }
out.print("\n"); out.print("\n");
} }

@ -312,7 +312,7 @@ static command_result hotkeys_cmd(color_ostream &out, vector <string> & paramete
if (Gui::getFocusString(top_screen) != "dfhack/viewscreen_hotkeys") if (Gui::getFocusString(top_screen) != "dfhack/viewscreen_hotkeys")
{ {
find_active_keybindings(top_screen); find_active_keybindings(top_screen);
Screen::show(new ViewscreenHotkeys(top_screen), plugin_self); Screen::show(dts::make_unique<ViewscreenHotkeys>(top_screen), plugin_self);
} }
} }
} }

@ -156,14 +156,14 @@ static command_result job_material_in_job(color_ostream &out, MaterialInfo &new_
if (item_mat != cur_mat) if (item_mat != cur_mat)
{ {
out.printerr("Job item %d has different material: %s\n", out.printerr("Job item %zu has different material: %s\n",
i, item_mat.toString().c_str()); i, item_mat.toString().c_str());
return CR_FAILURE; return CR_FAILURE;
} }
if (!new_mat.matches(*item)) if (!new_mat.matches(*item))
{ {
out.printerr("Job item %d requirements not satisfied by %s.\n", out.printerr("Job item %zu requirements not satisfied by %s.\n",
i, new_mat.toString().c_str()); i, new_mat.toString().c_str());
return CR_FAILURE; return CR_FAILURE;
} }

@ -1224,7 +1224,7 @@ private:
if (p1 || p2) if (p1 || p2)
{ {
df::unit* other = p1 ? act->unit_noble : act->unit_actor; df::unit* other = p1 ? act->unit_noble : act->unit_actor;
if (other && !(other->flags1.bits.dead || if (other && !(!Units::isActive(other) ||
(other->job.current_job && (other->job.current_job &&
(other->job.current_job->job_type == df::job_type::Sleep || (other->job.current_job->job_type == df::job_type::Sleep ||
other->job.current_job->job_type == df::job_type::Rest)) || other->job.current_job->job_type == df::job_type::Rest)) ||
@ -1247,7 +1247,7 @@ private:
for (auto u2 = world->units.active.begin(); u2 != world->units.active.end(); ++u2) for (auto u2 = world->units.active.begin(); u2 != world->units.active.end(); ++u2)
{ {
if ((*u2)->relationship_ids[df::unit_relationship_type::Mother] == dwarf->dwarf->id && if ((*u2)->relationship_ids[df::unit_relationship_type::Mother] == dwarf->dwarf->id &&
!(*u2)->flags1.bits.dead && Units::isActive(*u2) &&
((*u2)->profession == df::profession::CHILD || (*u2)->profession == df::profession::BABY)) ((*u2)->profession == df::profession::CHILD || (*u2)->profession == df::profession::BABY))
{ {
dwarf->has_children = true; dwarf->has_children = true;
@ -1757,7 +1757,7 @@ public:
} }
if (print_debug) if (print_debug)
out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); out.print("available count = %zu, distinct labors needed = %zu\n", available_dwarfs.size(), pq.size());
std::map<df::unit_labor, int> to_assign; std::map<df::unit_labor, int> to_assign;
@ -1958,7 +1958,7 @@ public:
/* Assign any leftover dwarfs to "standard" labors */ /* Assign any leftover dwarfs to "standard" labors */
if (print_debug) if (print_debug)
out.print("After assignment, %d dwarfs left over\n", available_dwarfs.size()); out.print("After assignment, %zu dwarfs left over\n", available_dwarfs.size());
for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++) for (auto d = available_dwarfs.begin(); d != available_dwarfs.end(); d++)
{ {

@ -188,8 +188,11 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
std::stringstream str; std::stringstream str;
print_prompt(str, cur_mode); print_prompt(str, cur_mode);
str << "# "; str << "# ";
if(out.lineedit(str.str(),input,liquids_hist) == -1) int rv;
return CR_FAILURE; while ((rv = out.lineedit(str.str(),input,liquids_hist))
== Console::RETRY);
if (rv <= Console::FAILURE)
return rv == Console::FAILURE ? CR_FAILURE : CR_OK;
liquids_hist.add(input); liquids_hist.add(input);
commands.clear(); commands.clear();

@ -0,0 +1,214 @@
local _ENV = mkmodule('plugins.cxxrandom')
function MakeNewEngine(seed)
if type(seed) == 'number' then
if seed == 0 then
print(":WARNING: Seeds equal to 0 are used if no seed is provided. This indicates to cxxrandom.plug.dll that the engine needs to be seeded with the current time.\nRecommendation: use a non-zero value for your seed, or don't provide a seed to use the time since epoch(1969~).")
end
return GenerateEngine(seed)
elseif type(seed) == 'nil' then
return GenerateEngine(0)
else
error("Argument `seed` must be a number, or nil.")
end
end
--Class: crng
-------------
crng = {}
function crng:new(engineID, destroyEngineOnDestruction, distrib)
local o = {}
self.__index = self
local idtype = type(engineID)
local flagtype = type(destroyEngineOnDestruction)
if idtype == 'number' then
o.rngID = engineID
elseif idtype == 'nil' then
o.rngID = GenerateEngine(0)
else
error("Invalid argument type (engineID): " .. tostring(engineID))
end
if flagtype ~= 'nil' and flagtype == 'boolean' then
o.destroyid = destroyEngineOnDestruction
elseif flagtype == 'nil' then
o.destroyid = true
else
error("Invalid arugment type (destroyEngineOnDestruction): " .. tostring(destroyEngineOnDestruction))
end
if type(distrib) ~= 'nil' then
if type(distrib) == 'table' and type(distrib.next) == 'function' then
o.distrib = distrib
o.distrib.rngID = o.rngID
else
error("Invalid distribution used as an argument. Cannot set this as the number distribution.")
end
end
setmetatable(o,self)
return o
end
--crng destructor - we may need to destroy the engine, the user may be doing it manually though
function crng:__gc()
if self.destroyid then
DestroyEngine(self.rngID)
end
end
function crng:changeSeed(seed)
if type(seed) == 'number' then
if seed == 0 then
print(":WARNING: Seeds equal to 0 are used if no seed is provided. This indicates to cxxrandom.plug.dll that the engine needs to be seeded with the current time.\nRecommendation: use a non-zero value for your seed, or don't provide a seed to use the time since epoch(1969~).")
end
return NewSeed(self.rngID, seed)
elseif type(seed) == 'nil' then
return NewSeed(self.rngID, 0)
else
error("Argument `seed` must be a number, or nil.")
end
end
function crng:setNumDistrib(distrib)
if type(distrib) == 'table' and type(distrib.next) == 'function' then
self.distrib = distrib
self.distrib.rngID = self.rngID
else
error("Invalid distribution used as an argument. Cannot set this as the number distribution.")
end
end
function crng:next()
if type(self.distrib) == 'table' and type(self.distrib.next) == 'function' then
return self.distrib:next(self.rngID)
else
error("crng object does not have a valid number distribution set")
end
end
function crng:shuffle()
if type(self.distrib) == 'table' and type(self.distrib.shuffle) == 'function' then
self.distrib:shuffle(self.rngID)
else
print(":WARNING: No self.distrib.shuffle not found.")
changeSeed(0)
end
end
--Class: normal_distribution
----------------------------
normal_distribution = {}
function normal_distribution:new(avg, stddev)
local o = {}
self.__index = self
if type(avg) ~= 'number' or type(stddev) ~= 'number' then
error("Invalid arguments in normal_distribution construction. Average and standard deviation must be numbers.")
end
o.average = avg
o.std_deviation = stddev
setmetatable(o,self)
return o
end
function normal_distribution:next(id)
return rollNormal(id, self.average, self.std_deviation)
end
--Class: real_distribution
----------------------------
real_distribution = {}
function real_distribution:new(min, max)
local o = {}
self.__index = self
if type(min) ~= 'number' or type(max) ~= 'number' then
error("Invalid arguments in real_distribution construction. min and max must be numbers.")
end
o.min = min
o.max = max
setmetatable(o,self)
return o
end
function real_distribution:next(id)
return rollDouble(id, self.min, self.max)
end
--Class: int_distribution
----------------------------
int_distribution = {}
function int_distribution:new(min, max)
local o = {}
self.__index = self
if type(min) ~= 'number' or type(max) ~= 'number' then
error("Invalid arguments in int_distribution construction. min and max must be numbers.")
end
o.min = min
o.max = max
setmetatable(o,self)
return o
end
function int_distribution:next(id)
return rollInt(id, self.min, self.max)
end
--Class: bool_distribution
----------------------------
bool_distribution = {}
function bool_distribution:new(chance)
local o = {}
self.__index = self
if type(min) ~= 'number' or type(max) ~= 'number' then
error("Invalid arguments in bool_distribution construction. min and max must be numbers.")
end
o.p = chance
setmetatable(o,self)
return o
end
function bool_distribution:next(id)
return rollBool(id, self.p)
end
--Class: num_sequence
----------------------------
num_sequence = {}
function num_sequence:new(a,b)
local o = {}
self.__index = self
local btype = type(b)
local atype = type(a)
if atype == 'number' and btype == 'number' then
if a == b then
print(":WARNING: You've provided two equal arguments to initialize your sequence with. This is the mechanism used to indicate to cxxrandom.plug.dll that an empty sequence is desired.\nRecommendation: provide no arguments if you wish for an empty sequence.")
end
o.seqID = MakeNumSequence(a, b)
elseif atype == 'table' then
o.seqID = MakeNumSequence(0,0)
for _,v in pairs(a) do
if type(v) ~= 'number' then
error("num_sequence can only be initialized using numbers. " .. tostring(v) .. " is not a number.")
end
AddToSequence(o.seqID, v)
end
elseif atype == "nil" and btype == "nil" then
o.seqID = MakeNumSequence(0,0)
else
error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b))
end
print("seqID:"..o.seqID)
setmetatable(o,self)
return o
end
function num_sequence:__gc()
DestroyNumSequence(self.seqID)
end
function num_sequence:add(x)
if type(x) ~= 'number' then
error("Cannot add non-number to num_sequence.")
end
AddToSequence(self.seqID, x)
end
function num_sequence:next()
return NextInSequence(self.seqID)
end
function num_sequence:shuffle()
if self.rngID == 'nil' then
error("Add num_sequence object to crng as distribution, before attempting to shuffle.")
end
ShuffleSequence(self.rngID, self.seqID)
end
return _ENV

@ -185,7 +185,14 @@ end
function job_outputs.MakeCrafts(callback, job) function job_outputs.MakeCrafts(callback, job)
local mat_type, mat_index, mat_mask = guess_job_material(job) local mat_type, mat_index, mat_mask = guess_job_material(job)
callback{ is_craft = true, mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask } callback{
is_craft = true,
item_type = -1,
item_subtype = -1,
mat_type = mat_type,
mat_index = mat_index,
mat_mask = mat_mask
}
end end
local plant_products = { local plant_products = {

@ -1157,7 +1157,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src, int cur
if (!Units::isOwnGroup(unit)) if (!Units::isOwnGroup(unit))
cur->allowEdit = false; cur->allowEdit = false;
if (unit->flags1.bits.dead) if (!Units::isActive(unit))
cur->allowEdit = false; cur->allowEdit = false;
if (unit->flags2.bits.visitor) if (unit->flags2.bits.visitor)
@ -1814,14 +1814,14 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (events->count(interface_key::CUSTOM_B)) if (events->count(interface_key::CUSTOM_B))
{ {
Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names), plugin_self); Screen::show(dts::make_unique<viewscreen_unitbatchopst>(units, true, &do_refresh_names), plugin_self);
} }
if (events->count(interface_key::CUSTOM_E)) if (events->count(interface_key::CUSTOM_E))
{ {
vector<UnitInfo*> tmp; vector<UnitInfo*> tmp;
tmp.push_back(cur); tmp.push_back(cur);
Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names), plugin_self); Screen::show(dts::make_unique<viewscreen_unitbatchopst>(tmp, false, &do_refresh_names), plugin_self);
} }
if (events->count(interface_key::CUSTOM_P)) if (events->count(interface_key::CUSTOM_P))
@ -1832,11 +1832,11 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
has_selected = true; has_selected = true;
if (has_selected) { if (has_selected) {
Screen::show(new viewscreen_unitprofessionset(units, true), plugin_self); Screen::show(dts::make_unique<viewscreen_unitprofessionset>(units, true), plugin_self);
} else { } else {
vector<UnitInfo*> tmp; vector<UnitInfo*> tmp;
tmp.push_back(cur); tmp.push_back(cur);
Screen::show(new viewscreen_unitprofessionset(tmp, false), plugin_self); Screen::show(dts::make_unique<viewscreen_unitprofessionset>(tmp, false), plugin_self);
} }
} }
@ -2187,7 +2187,7 @@ struct unitlist_hook : df::viewscreen_unitlistst
{ {
if (units[page].size()) if (units[page].size())
{ {
Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page]), plugin_self); Screen::show(dts::make_unique<viewscreen_unitlaborsst>(units[page], cursor_pos[page]), plugin_self);
return; return;
} }
} }

@ -96,6 +96,7 @@ command_result mode (color_ostream &out_, vector <string> & parameters)
string command = ""; string command = "";
bool set = false; bool set = false;
bool abuse = false; bool abuse = false;
int rv = 0;
t_gamemodes gm; t_gamemodes gm;
for(auto iter = parameters.begin(); iter != parameters.end(); iter++) for(auto iter = parameters.begin(); iter != parameters.end(); iter++)
{ {
@ -139,9 +140,10 @@ command_result mode (color_ostream &out_, vector <string> & parameters)
string selected; string selected;
input_again: input_again:
CommandHistory hist; CommandHistory hist;
out.lineedit("Enter new mode: ",selected, hist); while((rv = out.lineedit("Enter new mode: ",selected, hist))
if(selected == "c") == Console::RETRY);
return CR_OK; if(rv <= Console::FAILURE || selected == "c")
return rv == Console::FAILURE ? CR_FAILURE : CR_OK;
const char * start = selected.c_str(); const char * start = selected.c_str();
char * end = 0; char * end = 0;
select = strtol(start, &end, 10); select = strtol(start, &end, 10);
@ -178,14 +180,16 @@ command_result mode (color_ostream &out_, vector <string> & parameters)
{ {
CommandHistory hist; CommandHistory hist;
string selected; string selected;
out.lineedit("Enter new game mode number (c for exit): ",selected, hist); while ((rv = out.lineedit("Enter new game mode number (c for exit): ",selected, hist))
if(selected == "c") == Console::RETRY);
return CR_OK; if(rv <= Console::FAILURE || selected == "c")
return rv == Console::FAILURE ? CR_FAILURE : CR_OK;
const char * start = selected.c_str(); const char * start = selected.c_str();
gm.g_mode = (GameMode) strtol(start, 0, 10); gm.g_mode = (GameMode) strtol(start, 0, 10);
out.lineedit("Enter new game type number (c for exit): ",selected, hist); while((rv = out.lineedit("Enter new game type number (c for exit): ",selected, hist))
if(selected == "c") == Console::RETRY);
return CR_OK; if(rv <= Console::FAILURE || selected == "c")
return rv == Console::FAILURE ? CR_FAILURE : CR_OK;
start = selected.c_str(); start = selected.c_str();
gm.g_type = (GameType) strtol(start, 0, 10); gm.g_type = (GameType) strtol(start, 0, 10);
} }

@ -95,7 +95,7 @@ static vector<df::unit *> get_units_at(const df::coord pos, bool only_one)
df::unit_flags1 bad_flags; df::unit_flags1 bad_flags;
bad_flags.whole = 0; bad_flags.whole = 0;
bad_flags.bits.dead = true; bad_flags.bits.inactive = true;
bad_flags.bits.hidden_ambusher = true; bad_flags.bits.hidden_ambusher = true;
bad_flags.bits.hidden_in_ambush = true; bad_flags.bits.hidden_in_ambush = true;

@ -7,7 +7,7 @@
#include "modules/Filesystem.h" #include "modules/Filesystem.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "jsoncpp.h" #include "json/json.h"
#include "df/building.h" #include "df/building.h"
#include "df/historical_figure.h" #include "df/historical_figure.h"

Some files were not shown because too many files have changed in this diff Show More