Merge remote-tracking branch 'origin_DFHACK/develop' into twbt_experiments

develop
Warmist 2018-10-12 10:31:37 +03:00
commit 41856aef37
289 changed files with 19729 additions and 11364 deletions

4
.gitignore vendored

@ -56,3 +56,7 @@ tags
#VS is annoying about this one. #VS is annoying about this one.
/build/win64/DF_PATH.txt /build/win64/DF_PATH.txt
/build/win32/DF_PATH.txt /build/win32/DF_PATH.txt
/.vs
# custom plugins
/plugins/CMakeLists.custom.txt

13
.gitmodules vendored

@ -1,15 +1,18 @@
[submodule "plugins/stonesense"] [submodule "plugins/stonesense"]
path = plugins/stonesense path = plugins/stonesense
url = git://github.com/DFHack/stonesense.git url = ../../DFHack/stonesense.git
[submodule "plugins/isoworld"] [submodule "plugins/isoworld"]
path = plugins/isoworld path = plugins/isoworld
url = git://github.com/DFHack/isoworld.git url = ../../DFHack/isoworld.git
[submodule "library/xml"] [submodule "library/xml"]
path = library/xml path = library/xml
url = git://github.com/DFHack/df-structures.git url = ../../DFHack/df-structures.git
[submodule "depends/clsocket"] [submodule "depends/clsocket"]
path = depends/clsocket path = depends/clsocket
url = git://github.com/DFHack/clsocket.git url = ../../DFHack/clsocket.git
[submodule "scripts2"] [submodule "scripts2"]
path = scripts path = scripts
url = git://github.com/dfhack/scripts.git url = ../../DFHack/scripts.git
[submodule "depends/jsoncpp"]
path = depends/jsoncpp-sub
url = ../../open-source-parsers/jsoncpp.git

@ -3,13 +3,18 @@ language: cpp
cache: cache:
pip: true pip: true
directories: directories:
- $HOME/DF-travis
- $HOME/lua53 - $HOME/lua53
addons: addons:
apt: apt:
packages: &default_packages packages: &default_packages
- libsdl-image1.2-dev
- libsdl-ttf2.0-dev
- libsdl1.2-dev
- libxml-libxml-perl - libxml-libxml-perl
- libxml-libxslt-perl - libxml-libxslt-perl
- zlib1g-dev:i386 - ninja-build
- zlib1g-dev
matrix: matrix:
include: include:
- env: GCC_VERSION=4.8 - env: GCC_VERSION=4.8
@ -19,11 +24,16 @@ matrix:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
packages: packages:
- *default_packages - *default_packages
- gcc-4.8-multilib - gcc-4.8
- g++-4.8-multilib - g++-4.8
before_install: before_install:
- export DF_VERSION=$(sh travis/get-df-version.sh)
- export DF_FOLDER="$HOME/DF-travis/$DF_VERSION"
- pip install --user "sphinx==1.4" "requests[security]" - pip install --user "sphinx==1.4" "requests[security]"
- sh travis/build-lua.sh - sh travis/build-lua.sh
- sh travis/download-df.sh
- echo "export DFHACK_HEADLESS=1" >> "$HOME/.dfhackrc"
- echo "export DFHACK_DISABLE_CONSOLE=1" >> "$HOME/.dfhackrc"
script: script:
- export PATH="$PATH:$HOME/lua53/bin" - export PATH="$PATH:$HOME/lua53/bin"
- git tag tmp-travis-build - git tag tmp-travis-build
@ -37,8 +47,18 @@ script:
- python travis/script-syntax.py --ext=rb --cmd="ruby -c" - python travis/script-syntax.py --ext=rb --cmd="ruby -c"
- mkdir build-travis - mkdir build-travis
- cd build-travis - cd build-travis
- cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON - cmake .. -G Ninja -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER"
- make -j3 - ninja -j3 install
- mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init
- cd ..
- cp travis/dfhack_travis.init "$DF_FOLDER"/
- python travis/run-tests.py "$DF_FOLDER"
- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt"
before_cache:
- cat "$DF_FOLDER/stderr.log"
- rm -rf "$DF_FOLDER/hack"
- rm -rf "$DF_FOLDER/dfhack-config"
- rm -f "$DF_FOLDER"/*.log
notifications: notifications:
email: false email: false
irc: irc:

@ -4,12 +4,41 @@ https://github.com/Valloric/ycmd
# pylint: disable=import-error,invalid-name,missing-docstring,unused-argument # pylint: disable=import-error,invalid-name,missing-docstring,unused-argument
import os import os,platform
import ycm_core import ycm_core
def DirectoryOfThisScript(): def DirectoryOfThisScript():
return os.path.dirname(os.path.abspath(__file__)) return os.path.dirname(os.path.abspath(__file__))
default_flags = [
'-I','library/include',
'-I','library/proto',
'-I','plugins/proto',
'-I','depends/protobuf',
'-I','depends/lua/include',
'-I','depends/md5',
'-I','depends/jsoncpp/include',
'-I','depends/tinyxml',
'-I','depends/tthread',
'-I','depends/clsocket/src',
'-x','c++',
'-D','PROTOBUF_USE_DLLS',
'-D','LUA_BUILD_AS_DLL',
'-Wall','-Wextra',
]
if os.name == 'posix':
default_flags.extend([
'-D','LINUX_BUILD',
'-D','_GLIBCXX_USE_C99',
])
if platform.system() == 'Darwin':
default_flags.extend(['-D','_DARWIN'])
else:
default_flags.extend(['-D','_LINUX'])
else:
default_flags.extend(['-D','WIN32'])
# We need to tell YouCompleteMe how to compile this project. We do this using # We need to tell YouCompleteMe how to compile this project. We do this using
# clang's "Compilation Database" system, which essentially just dumps a big # clang's "Compilation Database" system, which essentially just dumps a big
# json file into the build folder. # json file into the build folder.
@ -117,13 +146,19 @@ def FlagsForFile(filename, **kwargs):
# python list, but a "list-like" StringVec object # python list, but a "list-like" StringVec object
compilation_info = GetCompilationInfoForFile(filename) compilation_info = GetCompilationInfoForFile(filename)
if not compilation_info: if not compilation_info:
return None return {
'flags':MakeRelativePathsInFlagsAbsolute(default_flags,DirectoryOfThisScript()),
'do_cache': True,
}
final_flags = MakeRelativePathsInFlagsAbsolute( final_flags = MakeRelativePathsInFlagsAbsolute(
compilation_info.compiler_flags_, compilation_info.compiler_flags_,
compilation_info.compiler_working_dir_ compilation_info.compiler_working_dir_
) )
# Make sure ycm reports more suspicuous code lines
final_flags.append('-Wextra')
return { return {
'flags': final_flags, 'flags': final_flags,
'do_cache': True 'do_cache': True

@ -1,24 +1,24 @@
# main project file. use it from a build sub-folder, see COMPILE for details # main project file. use it from a build sub-folder, see COMPILE for details
# prevent CMake warnings about INTERFACE_LINK_LIBRARIES vs LINK_INTERFACE_LIBRARIES
IF(CMAKE_VERSION VERSION_GREATER "2.8.12")
CMAKE_POLICY(SET CMP0022 OLD)
ENDIF()
# Set up build types # Set up build types
if(CMAKE_CONFIGURATION_TYPES) if(CMAKE_CONFIGURATION_TYPES)
SET(CMAKE_CONFIGURATION_TYPES Release RelWithDebInfo) SET(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo" CACHE STRING "List of supported configuration types" FORCE)
SET(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES}" CACHE STRING "List of supported configuration types" FORCE)
else(CMAKE_CONFIGURATION_TYPES) else(CMAKE_CONFIGURATION_TYPES)
set(DFHACK_TYPE_HELP "Choose the type of build, options are: Release and RelWithDebInfo")
# Prevent cmake C module attempts to overwrite our help string
if (NOT CMAKE_BUILD_TYPE) if (NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Release RelWithDebInfo.") SET(CMAKE_BUILD_TYPE "Release" CACHE STRING "${DFHACK_TYPE_HELP}")
else (NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "${DFHACK_TYPE_HELP}")
endif (NOT CMAKE_BUILD_TYPE) endif (NOT CMAKE_BUILD_TYPE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Release;RelWithDebInfo")
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 FATAL_ERROR) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
project(dfhack) project(dfhack)
macro(CHECK_GCC COMPILER_PATH) macro(CHECK_GCC COMPILER_PATH)
@ -48,23 +48,35 @@ if(UNIX)
endif() endif()
if(WIN32) if(WIN32)
if((NOT MSVC) OR (NOT MSVC_VERSION STREQUAL 1900)) if((NOT MSVC) OR (MSVC_VERSION LESS 1900) OR (MSVC_VERSION GREATER 1919))
message(SEND_ERROR "MSVC 2015 is required") message(SEND_ERROR "MSVC 2015 or 2017 is required")
endif() endif()
endif() endif()
if(MSVC) # Ask for C++11 standard from compilers
# disable C4819 code-page warning set(CMAKE_CXX_STANDARD 11)
add_definitions( "/wd4819" ) # Require the standard support from compilers.
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Use only standard c++ to keep code portable
set(CMAKE_CXX_EXTENSIONS OFF)
# Disable use of POSIX name warnings if(MSVC)
add_definitions ( "/D_CRT_NONSTDC_NO_WARNINGS") # disable C4819 code-page warning
add_definitions( "/wd4819" )
# supress C4503 - VC++ dislikes if a name is too long. If you get
# weird and mysterious linking errors, you can disable this, but you'll have to # Disable use of POSIX name warnings
# deal with a LOT of compiler noise over it add_definitions ( "/D_CRT_NONSTDC_NO_WARNINGS /D_CRT_SECURE_NO_WARNINGS")
# see https://msdn.microsoft.com/en-us/library/074af4b6.aspx
add_definitions( "/wd4503") # supress C4503 - VC++ dislikes if a name is too long. If you get
# weird and mysterious linking errors, you can disable this, but you'll have to
# deal with a LOT of compiler noise over it
# see https://msdn.microsoft.com/en-us/library/074af4b6.aspx
add_definitions( "/wd4503")
# suppress C4267 - VC++ complains whenever we implicitly convert an integer to
# a smaller type, and most of the time this is just conversion from 64 to 32 bits
# for things like vector sizes, which are never that big anyway.
add_definitions( "/wd4267")
endif() endif()
# Automatically detect architecture based on Visual Studio generator # Automatically detect architecture based on Visual Studio generator
@ -75,7 +87,7 @@ IF(MSVC AND NOT DEFINED DFHACK_BUILD_ARCH)
SET(DFHACK_BUILD_ARCH "32") SET(DFHACK_BUILD_ARCH "32")
ENDIF() ENDIF()
ELSE() ELSE()
SET(DFHACK_BUILD_ARCH "32" CACHE STRING "Architecture to build ('32' or '64')") SET(DFHACK_BUILD_ARCH "64" CACHE STRING "Architecture to build ('32' or '64')")
ENDIF() ENDIF()
IF("${DFHACK_BUILD_ARCH}" STREQUAL "32") IF("${DFHACK_BUILD_ARCH}" STREQUAL "32")
@ -129,6 +141,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.")
@ -140,12 +167,16 @@ 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.03") set(DF_VERSION "0.44.12")
SET(DFHACK_RELEASE "beta1") set(DFHACK_RELEASE "r1")
SET(DFHACK_PRERELEASE TRUE) set(DFHACK_PRERELEASE FALSE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
set(DFHACK_ABI_VERSION 1)
set(DFHACK_BUILD_ID "" CACHE STRING "Build ID (should be specified on command line)")
## where to install things (after the build is done, classic 'make install' or package structure) ## where to install things (after the build is done, classic 'make install' or package structure)
# the dfhack libraries will be installed here: # the dfhack libraries will be installed here:
IF(UNIX) IF(UNIX)
@ -187,8 +218,9 @@ IF(UNIX)
# ensure compatibility with older CPUs # ensure compatibility with older CPUs
# enable C++11 features # enable C++11 features
add_definitions(-DLINUX_BUILD) add_definitions(-DLINUX_BUILD)
add_definitions(-D_GLIBCXX_USE_C99)
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g -Wall -Wno-unused-variable") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -g -Wall -Wno-unused-variable")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -mtune=generic -std=c++0x") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -mtune=generic")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -mtune=generic") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -mtune=generic")
IF(DFHACK_BUILD_64) IF(DFHACK_BUILD_64)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -mno-avx")
@ -202,6 +234,7 @@ ELSEIF(MSVC)
# for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion # for msvc, tell it to always use 8-byte pointers to member functions to avoid confusion
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /vmg /vmm /MP")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od")
STRING(REPLACE "/O2" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
STRING(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") STRING(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
ENDIF() ENDIF()
@ -226,11 +259,11 @@ if(WIN32)
# Download zlib on Windows # Download zlib on Windows
set(ZLIB_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/depends/zlib/lib/win${DFHACK_BUILD_ARCH}) set(ZLIB_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/depends/zlib/lib/win${DFHACK_BUILD_ARCH})
if(${DFHACK_BUILD_ARCH} STREQUAL "64") if(${DFHACK_BUILD_ARCH} STREQUAL "64")
download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-zlib.lib" download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-zlib.lib"
${ZLIB_DOWNLOAD_DIR}/zlib.lib ${ZLIB_DOWNLOAD_DIR}/zlib.lib
"a3b2fc6b68efafa89b0882e354fc8418") "a3b2fc6b68efafa89b0882e354fc8418")
else() else()
download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-zlib.lib" download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-zlib.lib"
${ZLIB_DOWNLOAD_DIR}/zlib.lib ${ZLIB_DOWNLOAD_DIR}/zlib.lib
"f4ebaa21d9de28566e88b1edfcdff901") "f4ebaa21d9de28566e88b1edfcdff901")
endif() endif()
@ -247,11 +280,11 @@ if(WIN32)
# (DFHack doesn't require this at build time, so no need to move it to the build folder) # (DFHack doesn't require this at build time, so no need to move it to the build folder)
set(SDLREAL_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}) set(SDLREAL_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH})
if(${DFHACK_BUILD_ARCH} STREQUAL "64") if(${DFHACK_BUILD_ARCH} STREQUAL "64")
download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win64-SDL.dll" download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-SDL.dll"
${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll
"1ae242c4b94cb03756a1288122a66faf") "1ae242c4b94cb03756a1288122a66faf")
else() else()
download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/win32-SDL.dll" download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-SDL.dll"
${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll
"5a09604daca6b2b5ce049d79af935d6a") "5a09604daca6b2b5ce049d79af935d6a")
endif() endif()
@ -261,21 +294,55 @@ if(APPLE)
# libstdc++ (GCC 4.8.5 for OS X 10.6) # libstdc++ (GCC 4.8.5 for OS X 10.6)
# fixes crash-on-unwind bug in DF's libstdc++ # fixes crash-on-unwind bug in DF's libstdc++
set(LIBSTDCXX_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}) set(LIBSTDCXX_DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH})
if(${GCC_VERSION_OUT} VERSION_LESS "4.9")
set(LIBSTDCXX_GCC_VER "48")
else()
set(LIBSTDCXX_GCC_VER "7")
set(LIBSTDCXX_DOWNLOAD_DIR "${LIBSTDCXX_DOWNLOAD_DIR}-gcc7")
endif()
if(${DFHACK_BUILD_ARCH} STREQUAL "64") if(${DFHACK_BUILD_ARCH} STREQUAL "64")
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx64-libstdcxx.6.dylib.gz" if(${LIBSTDCXX_GCC_VER} STREQUAL "48")
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx64-gcc48-libstdcxx.6.dylib.gz"
"gz" "gz"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz
"cf26ed588be8e83c8e3a49919793b416" "cf26ed588be8e83c8e3a49919793b416"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib
"16dc6dbd4ecde7f9b95bb6dc91f07404") "16dc6dbd4ecde7f9b95bb6dc91f07404")
else() else()
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.43.05/osx32-libstdcxx.6.dylib.gz" # GCC 7
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx64-gcc7-libstdcxx.6.dylib.gz"
"gz"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz
"81314b7846f9e8806409bef2160c76e6"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib
"93b6cf4b01e9a9084a508fd6a4a88992")
endif()
else() # 32-bit
if(${LIBSTDCXX_GCC_VER} STREQUAL "48")
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-gcc48-libstdcxx.6.dylib.gz"
"gz" "gz"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz
"40f3d83871b114f0279240626311621b" "40f3d83871b114f0279240626311621b"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib
"c3f5678b8204917e03870834902c3e8b") "c3f5678b8204917e03870834902c3e8b")
else()
# GCC 7
download_file_unzip("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/osx32-gcc7-libstdcxx.6.dylib.gz"
"gz"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib.gz
"dbd213171f66edb90d204d525f10c969"
${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib
"b14c857e7e485a097c70a9ccd3132da7")
endif() endif()
endif()
install(PROGRAMS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib
DESTINATION ./hack/)
endif() endif()
#### expose depends #### #### expose depends ####
@ -303,7 +370,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"
@ -333,13 +399,14 @@ endif()
# build the lib itself # build the lib itself
IF(BUILD_LIBRARY) IF(BUILD_LIBRARY)
add_subdirectory (library) add_subdirectory (library)
install(FILES LICENSE.rst NEWS.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES LICENSE.rst docs/changelog.txt DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
file(WRITE "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" ${DFHACK_SETARCH}) file(WRITE "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" ${DFHACK_SETARCH})
install(FILES "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" DESTINATION "${DFHACK_DATA_DESTINATION}") install(FILES "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" DESTINATION "${DFHACK_DATA_DESTINATION}")
install(DIRECTORY dfhack-config/ DESTINATION dfhack-config/default) install(DIRECTORY dfhack-config/ DESTINATION dfhack-config/default)
install(DIRECTORY test DESTINATION "${DFHACK_DATA_DESTINATION}")
#build the plugins #build the plugins
IF(BUILD_PLUGINS) IF(BUILD_PLUGINS)
@ -356,6 +423,8 @@ if (BUILD_DOCS)
file(GLOB SPHINX_DEPS file(GLOB SPHINX_DEPS
"${CMAKE_CURRENT_SOURCE_DIR}/docs/*.rst" "${CMAKE_CURRENT_SOURCE_DIR}/docs/*.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/docs/changelog.txt"
"${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_changelog.py"
"${CMAKE_CURRENT_SOURCE_DIR}/docs/images/*.png" "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/*.png"
"${CMAKE_CURRENT_SOURCE_DIR}/docs/styles/*" "${CMAKE_CURRENT_SOURCE_DIR}/docs/styles/*"
"${CMAKE_CURRENT_SOURCE_DIR}/conf.py" "${CMAKE_CURRENT_SOURCE_DIR}/conf.py"
@ -368,7 +437,6 @@ if (BUILD_DOCS)
) )
set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS} set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS}
"${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst" "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/NEWS.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt"
) )
@ -396,6 +464,7 @@ if (BUILD_DOCS)
install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/html/ install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/html/
DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs
) )
install(FILES docs/_auto/news.rst docs/_auto/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION})
install(FILES "README.html" DESTINATION "${DFHACK_DATA_DESTINATION}") install(FILES "README.html" DESTINATION "${DFHACK_DATA_DESTINATION}")
endif() endif()
@ -415,9 +484,54 @@ IF(APPLE)
ELSE() ELSE()
set(DFHACK_PACKAGE_PLATFORM_NAME ${CMAKE_SYSTEM_NAME}) set(DFHACK_PACKAGE_PLATFORM_NAME ${CMAKE_SYSTEM_NAME})
ENDIF() ENDIF()
set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_NAME}-${DFHACK_BUILD_ARCH}${DFHACK_PACKAGE_SUFFIX}") # set on command line
if(DFHACK_BUILD_ID STREQUAL "")
set(DFHACK_BUILD_ID_PACKAGE "")
else()
set(DFHACK_BUILD_ID_PACKAGE "${DFHACK_BUILD_ID}-")
endif()
set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_BUILD_ID_PACKAGE}${DFHACK_PACKAGE_PLATFORM_NAME}-${DFHACK_BUILD_ARCH}${DFHACK_PACKAGE_SUFFIX}")
INCLUDE(CPack) INCLUDE(CPack)
OPTION(DFHACK_INCLUDE_CORE "Download and include Dwarf Fortress core files in DFHack. Useful for local testing, but should not be used in releases." OFF)
IF(DFHACK_INCLUDE_CORE)
STRING(REPLACE "." "_" DF_CORE_FILENAME "${DF_VERSION}")
STRING(REGEX REPLACE "^0_" "df_" DF_CORE_FILENAME "${DF_CORE_FILENAME}")
IF(UNIX)
IF(APPLE)
STRING(APPEND DF_CORE_FILENAME "_osx")
ELSE()
STRING(APPEND DF_CORE_FILENAME "_linux")
ENDIF()
IF(DFHACK_BUILD_32)
STRING(APPEND DF_CORE_FILENAME "32")
ENDIF()
STRING(APPEND DF_CORE_FILENAME ".tar.bz2")
ELSE()
STRING(APPEND DF_CORE_FILENAME "_win")
IF(DFHACK_BUILD_32)
STRING(APPEND DF_CORE_FILENAME "32")
ENDIF()
STRING(APPEND DF_CORE_FILENAME ".zip")
ENDIF()
SET(DF_CORE_URL "http://bay12games.com/dwarves/${DF_CORE_FILENAME}")
IF (NOT EXISTS "${CMAKE_BINARY_DIR}/${DF_CORE_FILENAME}")
FILE(DOWNLOAD "${DF_CORE_URL}" "${CMAKE_BINARY_DIR}/${DF_CORE_FILENAME}" SHOW_PROGRESS)
ENDIF()
FILE(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/df-core")
FILE(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/df-core")
IF(UNIX)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xjf "../${DF_CORE_FILENAME}" --strip-components=1
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/df-core")
ELSE()
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "../${DF_CORE_FILENAME}" --format=zip
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/df-core")
FILE(REMOVE "${CMAKE_BINARY_DIR}/df-core/SDL.dll")
ENDIF()
INSTALL(DIRECTORY "${CMAKE_BINARY_DIR}/df-core/"
DESTINATION .)
ENDIF()
#INCLUDE(FindSphinx.cmake) #INCLUDE(FindSphinx.cmake)
# Store old build arch # Store old build arch

@ -0,0 +1,350 @@
{
"environments": [
{
"environment": "msvc_2015_x86",
"PATH": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\BIN\\amd64_x86;${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\BIN\\amd64;${env.ProgramFiles(x86)}\\Windows Kits\\10\\bin\\x86;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\bin\\x86;${env.PATH}",
"VS140COMNTOOLS": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\Common7\\Tools\\",
"VCINSTALLDIR": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\",
"WindowsSdkDir": "${env.ProgramFiles(x86)}\\Windows Kits\\10\\",
"UCRTVersion": "10.0.10240.0",
"UniversalCRTSdkDir": "${env.ProgramFiles(x86)}\\Windows Kits\\10\\",
"LIB": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\LIB;${env.ProgramFiles(x86)}\\Windows Kits\\10\\lib\\10.0.10240.0\\ucrt\\x86;${env.ProgramFiles(x86)}\\Windows Kits\\10\\lib\\um\\x86;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\x86",
"INCLUDE": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\INCLUDE;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\shared;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\um;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\winrt;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\Include\\um;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\Include\\shared",
"LIBPATH": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\LIB"
},
{
"environment": "msvc_2015_x64",
"PATH": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\BIN\\amd64;${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\BIN;${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\BIN\\1033;${env.ProgramFiles(x86)}\\Windows Kits\\bin\\x64;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\bin\\x64;${env.PATH}",
"VS140COMNTOOLS": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\Common7\\Tools\\",
"VCINSTALLDIR": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\",
"WindowsSdkDir": "${env.ProgramFiles(x86)}\\Windows Kits\\10\\",
"UCRTVersion": "10.0.10240.0",
"UniversalCRTSdkDir": "${env.ProgramFiles(x86)}\\Windows Kits\\10\\",
"LIB": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\LIB\\amd64;${env.ProgramFiles(x86)}\\Windows Kits\\10\\lib\\10.0.10240.0\\ucrt\\x64;${env.ProgramFiles(x86)}\\Windows Kits\\10\\lib\\um\\x64;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\x64",
"INCLUDE": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\INCLUDE;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\10.0.10240.0\\ucrt;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\shared;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\um;${env.ProgramFiles(x86)}\\Windows Kits\\10\\include\\winrt;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\Include\\um;${env.ProgramFiles(x86)}\\Windows Kits\\8.1\\Include\\shared",
"LIBPATH": "${env.ProgramFiles(x86)}\\Microsoft Visual Studio 14.0\\VC\\LIB\\amd64"
}
],
"configurations": [
{
"name": "MSVC 32 Debug",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"inheritEnvironments": [ "msvc_x86_x64", "msvc_2015_x86" ],
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "32"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
},
{
"name": "REMOVE_SYMBOLS_FROM_DF_STUBS",
"value": "0"
},
{
"name": "DFHACK_INCLUDE_CORE",
"value": "1"
}
]
},
{
"name": "MSVC 32 Release",
"generator": "Ninja",
"configurationType": "Release",
"inheritEnvironments": [ "msvc_x86_x64", "msvc_2015_x86" ],
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "32"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
}
]
},
{
"name": "MSVC 64 Debug",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"inheritEnvironments": [ "msvc_x64_x64", "msvc_2015_x64" ],
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "64"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
},
{
"name": "REMOVE_SYMBOLS_FROM_DF_STUBS",
"value": "0"
},
{
"name": "DFHACK_INCLUDE_CORE",
"value": "1"
}
]
},
{
"name": "MSVC 64 Release",
"generator": "Ninja",
"configurationType": "Release",
"inheritEnvironments": [ "msvc_x64_x64", "msvc_2015_x64" ],
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "64"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
}
]
},
{
"name": "GCC 32 Debug",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/debug-32",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"intelliSenseMode": "linux-gcc-x86",
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "32"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
},
{
"name": "REMOVE_SYMBOLS_FROM_DF_STUBS",
"value": "0"
},
{
"name": "DFHACK_INCLUDE_CORE",
"value": "1"
}
]
},
{
"name": "GCC 32 Release",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/release-32",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "Release",
"intelliSenseMode": "linux-gcc-x86",
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "32"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
}
]
},
{
"name": "GCC 64 Debug",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/debug-64",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"intelliSenseMode": "linux-gcc-x64",
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "64"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
},
{
"name": "REMOVE_SYMBOLS_FROM_DF_STUBS",
"value": "0"
},
{
"name": "DFHACK_INCLUDE_CORE",
"value": "1"
}
]
},
{
"name": "GCC 64 Release",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/release-64",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "Release",
"intelliSenseMode": "linux-gcc-x64",
"variables": [
{
"name": "DFHACK_BUILD_ARCH",
"value": "64"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
}
]
},
{
"name": "GCC 4.8 32 Debug",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/debug-32-48",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"intelliSenseMode": "linux-gcc-x86",
"variables": [
{
"name": "CMAKE_C_COMPILER",
"value": "gcc-4.8"
},
{
"name": "CMAKE_CXX_COMPILER",
"value": "g++-4.8"
},
{
"name": "DFHACK_BUILD_ARCH",
"value": "32"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
},
{
"name": "REMOVE_SYMBOLS_FROM_DF_STUBS",
"value": "0"
},
{
"name": "DFHACK_INCLUDE_CORE",
"value": "1"
}
]
},
{
"name": "GCC 4.8 32 Release",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/release-32-48",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "Release",
"intelliSenseMode": "linux-gcc-x86",
"variables": [
{
"name": "CMAKE_C_COMPILER",
"value": "gcc-4.8"
},
{
"name": "CMAKE_CXX_COMPILER",
"value": "g++-4.8"
},
{
"name": "DFHACK_BUILD_ARCH",
"value": "32"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
}
]
},
{
"name": "GCC 4.8 64 Debug",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/debug-64-48",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"intelliSenseMode": "linux-gcc-x64",
"variables": [
{
"name": "CMAKE_C_COMPILER",
"value": "gcc-4.8"
},
{
"name": "CMAKE_CXX_COMPILER",
"value": "g++-4.8"
},
{
"name": "DFHACK_BUILD_ARCH",
"value": "64"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
},
{
"name": "REMOVE_SYMBOLS_FROM_DF_STUBS",
"value": "0"
},
{
"name": "DFHACK_INCLUDE_CORE",
"value": "1"
}
]
},
{
"name": "GCC 4.8 64 Release",
"remoteMachineName": "${defaultRemoteMachineName}",
"remoteCMakeListsRoot": "/tmp/dfhack-${workspaceHash}/src",
"remoteBuildRoot": "/tmp/dfhack-${workspaceHash}/release-64-48",
"rsyncCommandArgs": "--exclude=build --include=.git",
"remoteCopySources": true,
"cmakeExecutable": "$(which cmake)/..",
"generator": "Ninja",
"configurationType": "Release",
"intelliSenseMode": "linux-gcc-x64",
"variables": [
{
"name": "CMAKE_C_COMPILER",
"value": "gcc-4.8"
},
{
"name": "CMAKE_CXX_COMPILER",
"value": "g++-4.8"
},
{
"name": "DFHACK_BUILD_ARCH",
"value": "64"
},
{
"name": "BUILD_STONESENSE",
"value": "1"
}
]
}
]
}

@ -32,7 +32,8 @@ How to get new code into DFHack
(i.e. not the master or develop branch of your fork). (i.e. not the master or develop branch of your fork).
* If possible, compile on multiple platforms when changing anything that compiles * If possible, compile on multiple platforms when changing anything that compiles
* It must pass CI - run ``python travis/all.py`` to check this. * It must pass CI - run ``python travis/all.py`` to check this.
* Update ``NEWS.rst`` and ``docs/Authors.rst`` when applicable. * Update ``changelog.txt`` and ``docs/Authors.rst`` when applicable. See
`build-changelog` for more information on the changelog format.
* Create a GitHub pull request once finished * Create a GitHub pull request once finished
* Submit ideas and bug reports as :issue:`issues on GitHub <>`. * Submit ideas and bug reports as :issue:`issues on GitHub <>`.
Posts in the forum thread can easily get missed or forgotten. Posts in the forum thread can easily get missed or forgotten.
@ -48,16 +49,16 @@ In general, you'll need a good memory viewer and optionally something
to look at machine code without getting crazy :) to look at machine code without getting crazy :)
Using publicly known information and analyzing the game's data is preferred. Using publicly known information and analyzing the game's data is preferred.
Good windows tools include: Good Windows tools include:
* IDA Freeware 7.0 (for non-commercial use, supports 32-bit and 64-bit)
* Cheat Engine * Cheat Engine
* IDA Pro 5.0 (freely available for non-commercial use)
Good linux tools: Good Linux tools:
* angavrilov's df-structures gui (visit us on IRC for details). * angavrilov's df-structures gui (32-bit only, visit us on IRC for details)
* IDA Freeware 7.0 (see above)
* edb (Evan's Debugger) * edb (Evan's Debugger)
* IDA Pro 5.0 running under Wine
* Some of the tools residing in the ``legacy`` dfhack branch. * Some of the tools residing in the ``legacy`` dfhack branch.
Using the library as a developer Using the library as a developer

@ -6,7 +6,7 @@ Licenses
DFHack is distributed under the Zlib license, with some MIT- DFHack is distributed under the Zlib license, with some MIT-
and BSD-licensed components. These licenses protect your right and BSD-licensed components. These licenses protect your right
to use DFhack for any purpose, distribute copies, and so on. to use DFHack for any purpose, distribute copies, and so on.
The core, plugins, scripts, and other DFHack code all use the The core, plugins, scripts, and other DFHack code all use the
ZLib license unless noted otherwise. By contributing to DFHack, ZLib license unless noted otherwise. By contributing to DFHack,
@ -26,6 +26,7 @@ jsoncpp_ MIT \(c\) 2007-2010, Baptiste Lepilleur
linenoise_ BSD 2-clause \(c\) 2010, Salvatore Sanfilippo & Pieter Noordhuis linenoise_ BSD 2-clause \(c\) 2010, Salvatore Sanfilippo & Pieter Noordhuis
lua_ MIT \(c\) 1994-2008, Lua.org, PUC-Rio. lua_ MIT \(c\) 1994-2008, Lua.org, PUC-Rio.
luafilesystem_ MIT \(c\) 2003-2014, Kepler Project luafilesystem_ MIT \(c\) 2003-2014, Kepler Project
lua-profiler_ MIT \(c\) 2002,2003,2004 Pepperfish
protobuf_ BSD 3-clause \(c\) 2008, Google Inc. protobuf_ BSD 3-clause \(c\) 2008, Google Inc.
tinythread_ Zlib \(c\) 2010, Marcus Geelnard tinythread_ Zlib \(c\) 2010, Marcus Geelnard
tinyxml_ Zlib \(c\) 2000-2006, Lee Thomason tinyxml_ Zlib \(c\) 2000-2006, Lee Thomason
@ -44,6 +45,7 @@ UTF-8-decoder_ MIT \(c\) 2008-2010, Bjoern Hoehrmann
.. _tinythread: http://tinythreadpp.bitsnbites.eu/ .. _tinythread: http://tinythreadpp.bitsnbites.eu/
.. _tinyxml: http://www.sourceforge.net/projects/tinyxml .. _tinyxml: http://www.sourceforge.net/projects/tinyxml
.. _UTF-8-decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa .. _UTF-8-decoder: http://bjoern.hoehrmann.de/utf-8/decoder/dfa
.. _lua-profiler: http://lua-users.org/wiki/PepperfishProfiler
.. _CC-BY-SA: http://creativecommons.org/licenses/by/3.0/deed.en_US .. _CC-BY-SA: http://creativecommons.org/licenses/by/3.0/deed.en_US

@ -1,911 +0,0 @@
.. comment
This is the changelog file for DFHack. If you add or change anything, note
it here under the heading "DFHack Future", in the appropriate section.
Items within each section are listed in alphabetical order to minimise merge
conflicts. Try to match the style and level of detail of the other entries.
This file should not contain details specific to prereleases, but it should
contain changes from previous stable releases. For example, if a bug was
introduced in one alpha version and fixed in another, do not include it
here.
Sections for each release are added as required, and consist solely of the
following in order as subheadings::
New [Internal Commands | Plugins | Scripts | Tweaks | Features]
Fixes
Misc Improvements
Removed
Internals
Lua
Ruby
When referring to a script, plugin, or command, use backticks (```) to
create a link to the relevant documentation - and check that the docs are
still up to date!
When adding a new release, change "DFHack future" to the appropriate title
before releasing, and then add a new "DFHack future" section after releasing.
.. _changelog:
#########
Changelog
#########
.. contents::
:depth: 2
DFHack future
=============
New Scripts
-----------
- `devel/check-other-ids`: Checks the validity of "other" vectors in the
``world`` global
- `gui/cp437-table`: An in-game CP437 table
Fixes
-----
- Fixed issues with the console output color affecting the prompt on Windows
- `createitem`: stopped items from teleporting away in some forts
- `gui/gm-unit`: can now edit mining skill
- `gui/quickcmd`: stopped error from adding too many commands
Misc Improvements
-----------------
- The console now provides suggestions for built-in commands
- `devel/export-dt-ini`: avoid hardcoding flags
- `exportlegends`:
- reordered some tags to match DF's order
- added progress indicators for exporting long lists
- `gui/gm-editor`: added enum names to enum edit dialogs
- `gui/gm-unit`: made skill search case-insensitive
- `gui/pathable`: added tile types to sidebar
- `gui/rename`: added "clear" and "special characters" options
- `modtools/skill-change`:
- now updates skill levels appropriately
- only prints output if ``-loud`` is passed
- `remotefortressreader`: includes item stack sizes and some performance improvements
Removed
-------
- `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01
Lua
---
- Added a new ``dfhack.console`` API
- Exposed ``get_vector()`` (from C++) for all types that support ``find()``,
e.g. ``df.unit.get_vector() == df.global.world.units.all``
- Improved ``json`` I/O error messages
- Stopped a crash when trying to create instances of classes whose vtable
addresses are not available
DFHack 0.43.05-r3
=================
Internals
---------
- Fixed an uncommon crash that could occur when printing text to the console
- Added lots of previously-missing DF classes
- More names for fields: https://github.com/DFHack/df-structures/compare/0.43.05-r2...0.43.05
Fixes
-----
- Linux: fixed argument to ``setarch`` in the ``dfhack`` launcher script
- Ruby: fixed an error that occurred when the DF path contained an apostrophe
- `diggingInvaders` now compiles again and is included
- `labormanager`:
- stopped waiting for on-duty military dwarves with minor injuries to obtain care
- stopped waiting for meetings when participant(s) are dead
- fixed a crash for dwarves with no cultural identity
- `luasocket`: fixed ``receive()`` with a byte count
- `orders`: fixed an error when importing orders with material categories
- `siren`: fixed an error
- `stockpiles`: fixed serialization of barrel and bin counts
- `view-item-info`: fixed a ``CHEESE_MAT``-related error
Misc Improvements
-----------------
- `devel/export-dt-ini`: added more offsets for new DT versions
- `digfort`: added support for changing z-levels
- `exportlegends`: suppressed ABSTRACT_BUILDING warning
- `gui/dfstatus`: excluded logs in constructions
- `labormanager`:
- stopped assigning woodcutting jobs to elves
- "recover wounded" jobs now weighted based on altruism
- `remotefortressreader`: added support for buildings, grass, riders, and
hair/beard styles
DFHack 0.43.05-r2
=================
Internals
---------
- Rebuilding DFHack can be faster if nothing Git-related has changed
- Plugins can now hook Screen::readTile()
- Improved Lua compatibility with plugins that hook into GUI functions (like TWBT)
- Expanded focus strings for jobmanagement and workquota_condition viewscreens
- ``Gui::getAnyUnit()``: added support for viewscreen_unitst,
viewscreen_textviewerst, viewscreen_layer_unit_relationshipst
- Fixed (limited) keybinding support in PRINT_MODE:TEXT on macOS
- Added a new standardized ``Gui::refreshSidebar()`` function to fix behavior of
some plugins on the lowest z-level
- New ``Buildings`` module functions: ``markedForRemoval()``, ``getCageOccupants()``
- Limited recursive command invocations to 20 to prevent crashes
- Added an ``onLoad.init-example`` file
Lua
---
- Improved C++ exception handling for some native functions that aren't direct
wrappers around C++ functions (in this case, error messages could be nil and
cause the Lua interpreter to quit)
- Added support for a ``key_pen`` option in Label widgets
- Fixed ``to_first`` argument to ``dfhack.screen.dismiss()``
- Added optional ``map`` parameters to some screen functions
- Exposed some more functions to Lua:
- ``dfhack.gui.refreshSidebar()``
- ``dfhack.gui.getAnyUnit()``
- ``dfhack.gui.getAnyBuilding()``
- ``dfhack.gui.getAnyItem()``
- ``dfhack.gui.getAnyPlant()``
- ``dfhack.gui.getDepthAt()``
- ``dfhack.units.getUnitsInBox()``
- ``dfhack.units.isVisible()``
- ``dfhack.maps.isTileVisible()``
- ``dfhack.buildings.markedForRemoval()``
- ``dfhack.buildings.getCageOccupants()``
- ``dfhack.internal.md5()``
- ``dfhack.internal.md5File()``
- ``dfhack.internal.threadid()``
- New function: ``widgets.Pages:getSelectedPage()``
- Added a ``key`` option to EditField and FilteredList widgets
- Fixed an issue preventing ``repeatUtil.cancel()`` from working when called
from the callback
Ruby
----
- Fixed a crash when creating new instances of DF virtual classes (e.g. fixes a
`lever` crash)
- Ruby scripts can now be loaded from any script paths specified (from script-
paths.txt or registered through the Lua API)
- ``unit_find()`` now uses ``Gui::getSelectedUnit()`` and works in more places
(e.g. `exterminate` now works from more screens, like `command-prompt`)
New Internal Commands
---------------------
- `alias`: allows configuring aliases for other commands
New Plugins
-----------
- `orders`: Manipulate manager orders
- `pathable`: Back-end for `gui/pathable`
New Scripts
-----------
- `clear-smoke`: Removes all smoke from the map
- `empty-bin`: Empty a bin onto the floor
- `fix/retrieve-units`: Spawns stuck invaders/guests
- `fix/stuck-merchants`: Dismisses stuck merchants that haven't entered the map yet
- `gui/pathable`: View whether tiles on the map can be pathed to
- `gui/teleport`: A front-end for the `teleport` script
- `warn-stuck-trees`: Detects citizens stuck in trees
New Tweaks
----------
- `tweak` burrow-name-cancel: Implements the "back" option when renaming a
burrow, which currently does nothing (:bug:`1518`)
- `tweak` cage-butcher: Adds an option to butcher units when viewing cages with "q"
Fixes
-----
- Enforced use of ``stdout.log`` and ``stderr.log`` (instead of their ``.txt``
counterparts) on Windows
- Fixed ``getItemBaseValue()`` for cheese, sheets and instruments
- Fixed alignment in:
- ``viewscreen_choose_start_sitest``
- ``viewscreen_export_graphical_mapst``
- ``viewscreen_setupadventurest``
- ``viewscreen_setupdwarfgamest``
- `adv-max-skills`: fixed error due to viewscreen changes
- `autolabor`: fixed a crash when assigning haulers while traders are active
- `buildingplan`: fixed an issue that prevented certain numbers from being used
in building names
- `confirm`:
- dialogs are now closed permanently when disabled from the settings UI
- fixed an issue that could have prevented closing dialogs opened by pressing "s"
- `embark-tools`: stopped the sand indicator from overlapping dialogs
- `exportlegends`: fixed some crashes and site map issues
- `devel/find-offsets`: fixed ``current_weather`` scan
- `gui/extended-status`: fixed an error when no beds are available
- `gui/family-affairs`: fixed issues with assigning lovers
- `gui/gm-editor`:
- made keybinding display order consistent
- stopped keys from performing actions in help screen
- `gui/manager-quantity`:
- now allows orders with a limit of 0
- fixed screen detection
- `gui/mechanisms`, `gui/room-list`: fixed an issue when recentering the map when exiting
- `lever`: prevented pulling non-lever buildings, which can cause crashes
- `markdown`: fixed file encoding
- `modtools/create-unit`:
- fixed when popup announcements are present
- added checks to ensure that the current game mode is restored
- `resume`: stopped drawing on the map border
- `show-unit-syndromes`: fixed an error when handling some syndromes
- `strangemood`: fixed some issues with material searches
- `view-item-info`: fixed a color-related error for some materials
Misc Improvements
-----------------
- Docs: prevented automatic hyphenation in some browsers, which was producing
excessive hyphenation sometimes
- `command-prompt`: invoking ``command-prompt`` a second time now hides the prompt
- `gui/extended-status`: added an option to assign/replace the manager
- `gui/load-screen`:
- adjusted dialog width for long folder names
- added modification times and DF versions to dialog
- `gui/mechanisms`, `gui/room-list`, `gui/siege-engine`: add and list "exit to map" options
- `lever`: added support for pulling levers at high priority
- `markdown`: now recognizes ``-n`` in addition to ``/n``
- `remotefortressreader`: more data exported, used by Armok Vision v0.17.0
- `resume`, `siege-engine`: improved compatibility with GUI-hooking plugins (like TWBT)
- `sc-script`: improved help text
- `teleport`: can now be used as a module
- `tweak` embark-profile-name: now enabled in ``dfhack.init-example``
- `tweak` hotkey-clear: fixed display on larger screens
DFHack 0.43.05-r1
=================
Internals
---------
- 64-bit support on all platforms
- Several structure fixes to match 64-bit DF's memory layout
- Added ``DFHack::Job::removeJob()`` function
- New module: ``Designations`` - handles designation creation (currently for plants only)
- Added ``Gui::getSelectedPlant()``
- Added ``Units::getMainSocialActivity()``, ``Units::getMainSocialEvent()``
- Visual Studio 2015 now required to build on Windows instead of 2010
- GCC 4.8 or newer required to build on Linux and OS X (and now supported on OS X)
- Updated TinyXML from 2.5.3 to 2.6.2
- Added the ability to download files manually before building
Lua
---
- Lua has been updated to 5.3 - see http://www.lua.org/manual/5.3/readme.html for details
- Floats are no longer implicitly converted to integers in DFHack API calls
- ``df.new()`` supports more types: ``char``, ``intptr_t``, ``uintptr_t``, ``long``, ``unsigned long``
- String representations of vectors and a few other containers now include their lengths
- Added a ``tile-material`` module
- Added a ``Painter:key_string()`` method
- Made ``dfhack.gui.revealInDwarfmodeMap()`` available
Ruby
----
- Added support for loading ruby 2.x libraries
New Plugins
-----------
- `dwarfvet` enables animal caretaking
- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs
- `labormanager` (formerly autolabor2): a more advanced alternative to `autolabor`
- `misery`: re-added and updated for the 0.4x series
- `title-folder`: shows DF folder name in window title bar when enabled
New Scripts
-----------
- `adv-rumors`: improves the "Bring up specific incident or rumor" menu in adventure mode
- `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile.
- `install-info`: Logs basic troubleshooting information about the current DFHack installation
- `load-save`: loads a save non-interactively
- `modtools/change-build-menu`: Edit the build mode sidebar menus
- `modtools/if-entity`: Run a command if the current entity matches a given ID
- `season-palette`: Swap color palettes with the changes of the seasons
- `unforbid`: Unforbids all items
New Tweaks
----------
- `tweak condition-material <tweak>`: fixes a crash in the work order condition material list
- `tweak hotkey-clear <tweak>`: adds an option to clear bindings from DF hotkeys
Fixes
-----
- The DF path on OS X can now contain spaces and ``:`` characters
- Buildings::setOwner() changes now persist properly when saved
- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable
- Fixed ``plug`` output alignment for plugins with long names
- `add-thought`: fixed support for emotion names
- `autochop`:
- fixed several issues with job creation and removal
- stopped designating the center tile (unreachable) for large trees
- stopped options from moving when enabling and disabling burrows
- fixed display of unnamed burrows
- `devel/find-offsets`: fixed a crash when vtables used by globals aren't available
- `getplants`:
- fixed several issues with job creation and removal
- stopped designating the center tile (unreachable) for large trees
- `gui/workflow`: added extra keybinding to work with `gui/extended-status`
- `manipulator`:
- Fixed crash when selecting a profession from an empty list
- Custom professions are now sorted alphabetically more reliably
- `modtools/create-item`:
- made gloves usable by specifying handedness
- now creates pairs of boots and gloves
- `modtools/create-unit`:
- stopped permanently overwriting the creature creation menu in arena mode
- now uses non-English names
- added ``-setUnitToFort`` option to make a unit a civ/group member more easily
- fixed some issues where units would appear in unrevealed areas of the map
- `modtools/item-trigger`: fixed errors with plant growths
- `remotefortressreader`: fixed a crash when serializing the local map
- `ruby`: fixed a crash when unloading the plugin on Windows
- `stonesense`: disabled overlay in STANDARD-based print modes to prevent crashes
- `title-version`: now hidden when loading an arena
Misc Improvements
-----------------
- Documented all default keybindings (from :file:`dfhack.init-example`) in the
docs for the relevant commands; updates enforced by build system.
- `autounsuspend`: reduced update frequency to address potential performance issues
- `gui/extended-status`: added a feature to queue beds
- `lua` and `gui/gm-editor` now support the same aliases (``scr``, ``unit``, etc.)
- `manipulator`: added social activities to job column
- `remotefortressreader`: Added support for
- world map snow coverage
- spatters
- wall info
- site towers, world buildings
- surface material
- building items
- DF version info
- `title-version`: Added a prerelease indicator
- `workflow`: Re-added ``Alt-W`` keybindings
DFHack 0.43.03-r1
=================
Lua
---
- Label widgets can now easily register handlers for mouse clicks
New Features
------------
- `add-thought`: allow syndrome name as ``-thought`` argument
- `gui/gm-editor`
- Added ability to insert default types into containers. For primitive types leave the type entry empty, and for references use ``*``.
- Added ``shift-esc`` binding to fully exit from editor
- Added ``gui/gm-editor toggle`` command to toggle editor visibility (saving position)
- `modtools/create-unit`:
- Added an option to attach units to an existing wild animal population
- Added an option to attach units to a map feature
Fixes
-----
- `autofarm`: Can now handle crops that grow for more than a season
- `combine-plants`: Fixed recursion into sub-containers
- `createitem`: Now moves multiple created items to cursor correctly
- `exportlegends`: Improved handling of unknown enum items (fixes many errors)
- `gui/create-item`: Fixed quality when creating multiple items
- `gui/mod-manager`: Fixed error when mods folder doesn't exist
- `modtools/item-trigger`: Fixed handling of items with subtypes
- `reveal`: ``revflood`` now handles constructed stairs with floors in generated fortresses
- `stockflow`:
- Can order metal mechanisms
- Fixed material category of thread-spinning jobs
Misc Improvements
-----------------
- The built-in ``ls`` command now wraps the descriptions of commands
- `catsplosion`: now a lua script instead of a plugin
- `fix/diplomats`: replaces ``fixdiplomats``
- `fix/merchants`: replaces ``fixmerchants``
- `prefchange`: added a ``help`` option
- `probe`: now displays raw tiletype names
- Unified script documentation and in-terminal help options
Removed
-------
- `tweak` manager-quantity: no longer needed
DFHack 0.42.06-r1
=================
Internals
---------
- Commands to run on startup can be specified on the command line with ``+``
Example::
./dfhack +devel/print-args example
"Dwarf Fortress.exe" +devel/print-args example
- Prevented plugins with active viewscreens from being unloaded and causing a crash
- Additional script search paths can be specified in dfhack-config/script-paths.txt
Lua
---
- `building-hacks` now supports ``auto_gears`` flags. It automatically finds and animates gears in building definition
- Changed how `eventful` triggers reaction complete. Now it has ``onReactionComplete`` and ``onReactionCompleting``. Second one can be canceled
New Plugins
-----------
- `autogems`: Creates a new Workshop Order setting, automatically cutting rough gems
New Scripts
-----------
- `devel/save-version`: Displays DF version information about the current save
- `modtools/extra-gamelog`: replaces ``log-region``, ``soundsense-season``, and ``soundsense``
New Features
------------
- `buildingplan`: Support for floodgates, grates, and bars
- `colonies`: new ``place`` subcommand and supports any vermin (default honey bees)
- `confirm`: Added a confirmation for retiring locations
- `exportlegends`: Exports more information (poetic/musical/dance forms, written/artifact content, landmasses, extra histfig information, and more)
- `search-plugin`: Support for new screens:
- location occupation assignment
- civilization animal training knowledge
- animal trainer assignment
- `tweak`:
- ``tweak block-labors``: Prevents labors that can't be used from being toggled
- ``tweak hide-priority``: Adds an option to hide designation priority indicators
- ``tweak title-start-rename``: Adds a safe rename option to the title screen "Start Playing" menu
- `zone`:
- Added ``unassign`` subcommand
- Added ``only`` option to ``assign`` subcommand
Fixes
-----
- Fixed a crash bug caused by the historical figures DFHack uses to store persistent data.
- More plugins should recognize non-dwarf citizens
- Fixed a possible crash from cloning jobs
- moveToBuilding() now sets flags for items that aren't a structural part of the building properly
- `autotrade`, `stocks`: Made trading work when multiple caravans are present but only some can trade
- `confirm` note-delete: No longer interferes with name entry
- `exportlegends`: Handles entities without specific races, and a few other fixes for things new to v0.42
- `fastdwarf`: Fixed a bug involving teleporting mothers but not the babies they're holding.
- `gaydar`: Fixed text display on OS X/Linux and failure with soul-less creatures
- `manipulator`:
- allowed editing of non-dwarf citizens
- stopped ghosts and visitors from being editable
- fixed applying last custom profession
- `modtools/create-unit`: Stopped making units without civs historical figures
- `modtools/force`:
- Removed siege option
- Prevented a crash resulting from a bad civilization option
- `showmood`: Fixed name display on OS X/Linux
- `view-item-info`: Fixed density units
Misc Improvements
-----------------
- `autochop`: Can now edit log minimum/maximum directly and remove limit entirely
- `autolabor`, `autohauler`, `manipulator`: Added support for new jobs/labors/skills
- `colonies`: now implemented by a script
- `createitem`: Can now create items anywhere without specifying a unit, as long as a unit exists on the map
- `devel/export-dt-ini`: Updated for 0.42.06
- `devel/find-offsets`: Automated several more scans
- `gui/gm-editor`: Now supports finding some items with a numeric ID (with ``i``)
- `lua`: Now supports some built-in variables like `gui/gm-editor`, e.g. ``unit``, ``screen``
- `remotefortressreader`: Can now trigger keyboard events
- `stockflow`: Now offers better control over individual craft jobs
- `weather`: now implemented by a script
- `zone`: colored output
Removed
-------
- DFusion: legacy script system, obsolete or replaced by better alternatives
DFHack 0.40.24-r5
=================
New Features
------------
- `confirm`:
- Added a ``uniform-delete`` option for military uniform deletion
- Added a basic in-game configuration UI
Fixes
-----
- Fixed a rare crash that could result from running `keybinding` in onLoadWorld.init
- Script help that doesn't start with a space is now recognized correctly
- `confirm`: Fixed issues with haul-delete, route-delete, and squad-disband confirmations intercepting keys too aggressively
- `emigration` should work now
- `fix-unit-occupancy`: Significantly optimized - up to 2,000 times faster in large fortresses
- `gui/create-item`: Allow exiting quantity prompt
- `gui/family-affairs`: Fixed an issue where lack of relationships wasn't recognized and other issues
- `modtools/create-unit`: Fixed a possible issue in reclaim fortress mode
- `search-plugin`: Fixed a crash on the military screen
- `tweak` max-wheelbarrow: Fixed a minor display issue with large numbers
- `workflow`: Fixed a crash related to job postings (and added a fix for existing, broken jobs)
Misc Improvements
-----------------
- Unrecognized command feedback now includes more information about plugins
- `fix/dry-buckets`: replaces the ``drybuckets`` plugin
- `feature`: now implemented by a script
DFHack 0.40.24-r4
=================
Internals
---------
- A method for caching screen output is now available to Lua (and C++)
- Developer plugins can be ignored on startup by setting the ``DFHACK_NO_DEV_PLUGINS`` environment variable
- The console on Linux and OS X now recognizes keyboard input between prompts
- JSON libraries available (C++ and Lua)
- More DFHack build information used in plugin version checks and available to plugins and lua scripts
- Fixed a rare overflow issue that could cause crashes on Linux and OS X
- Stopped DF window from receiving input when unfocused on OS X
- Fixed issues with keybindings involving :kbd:`Ctrl`:kbd:`A` and :kbd:`Ctrl`:kbd:`Z`,
as well as :kbd:`Alt`:kbd:`E`/:kbd:`U`/:kbd:`N` on OS X
- Multiple contexts can now be specified when adding keybindings
- Keybindings can now use :kbd:`F10`-:kbd:`F12` and :kbd:`0`-:kbd:`9`
- Plugin system is no longer restricted to plugins that exist on startup
- :file:`dfhack.init` file locations significantly generalized
Lua
---
- Scripts can be enabled with the built-in `enable`/`disable <disable>` commands
- A new function, ``reqscript()``, is available as a safer alternative to ``script_environment()``
- Lua viewscreens can choose not to intercept the OPTIONS keybinding
New internal commands
---------------------
- `kill-lua`: Interrupt running Lua scripts
- `type`: Show where a command is implemented
New plugins
-----------
- `confirm`: Adds confirmation dialogs for several potentially dangerous actions
- `fix-unit-occupancy`: Fixes issues with unit occupancy, such as faulty "unit blocking tile" messages (:bug:`3499`)
- `title-version` (formerly ``vshook``): Display DFHack version on title screen
New scripts
-----------
- `armoks-blessing`: Adjust all attributes, personality, age and skills of all dwarves in play
- `brainwash`: brainwash a dwarf (modifying their personality)
- `burial`: sets all unowned coffins to allow burial ("-pets" to allow pets too)
- `deteriorateclothes`: make worn clothes on the ground wear far faster to boost FPS
- `deterioratecorpses`: make body parts wear away far faster to boost FPS
- `deterioratefood`: make food vanish after a few months if not used
- `elevate-mental`: elevate all the mental attributes of a unit
- `elevate-physical`: elevate all the physical attributes of a unit
- `emigration`: stressed dwarves may leave your fortress if they see a chance
- `fix-ster`: changes fertility/sterility of animals or dwarves
- `gui/family-affairs`: investigate and alter romantic relationships
- `make-legendary`: modify skill(s) of a single unit
- `modtools/create-unit`: create new units from nothing
- `modtools/equip-item`: a script to equip items on units
- `points`: set number of points available at embark screen
- `pref-adjust`: Adjust all preferences of all dwarves in play
- `rejuvenate`: make any "old" dwarf 20 years old
- `starvingdead`: make undead weaken after one month on the map, and crumble after six
- `view-item-info`: adds information and customisable descriptions to item viewscreens
- `warn-starving`: check for starving, thirsty, or very drowsy units and pause with warning if any are found
New tweaks
----------
- embark-profile-name: Allows the use of lowercase letters when saving embark profiles
- kitchen-keys: Fixes DF kitchen meal keybindings
- kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences
- kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs
Fixes
-----
- Plugins with vmethod hooks can now be reloaded on OS X
- Lua's ``os.system()`` now works on OS X
- Fixed default arguments in Lua gametype detection functions
- Circular lua dependencies (reqscript/script_environment) fixed
- Prevented crash in ``Items::createItem()``
- `buildingplan`: Now supports hatch covers
- `gui/create-item`: fixed assigning quality to items, made :kbd:`Esc` work properly
- `gui/gm-editor`: handles lua tables properly
- `help`: now recognizes built-in commands, like ``help``
- `manipulator`: fixed crash when selecting custom professions when none are found
- `remotefortressreader`: fixed crash when attempting to send map info when no map was loaded
- `search-plugin`: fixed crash in unit list after cancelling a job; fixed crash when disabling stockpile category after searching in a subcategory
- `stockpiles`: now checks/sanitizes filenames when saving
- `stocks`: fixed a crash when right-clicking
- `steam-engine`: fixed a crash on arena load; number keys (e.g. 2/8) take priority over cursor keys when applicable
- tweak fps-min fixed
- tweak farm-plot-select: Stopped controls from appearing when plots weren't fully built
- `workflow`: Fixed some issues with stuck jobs. Existing stuck jobs must be cancelled and re-added
- `zone`: Fixed a crash when using ``zone set`` (and a few other potential crashes)
Misc Improvements
-----------------
- DFHack documentation:
- massively reorganised, into files of more readable size
- added many missing entries
- indexes, internal links, offline search all documents
- includes documentation of linked projects (df-structures, third-party scripts)
- better HTML generation with Sphinx
- documentation for scripts now located in source files
- `autolabor`:
- Stopped modification of labors that shouldn't be modified for brokers/diplomats
- Prioritize skilled dwarves more efficiently
- Prevent dwarves from running away with tools from previous jobs
- `automaterial`: Fixed several issues with constructions being allowed/disallowed incorrectly when using box-select
- `dwarfmonitor`:
- widgets' positions, formats, etc. are now customizable
- weather display now separated from the date display
- New mouse cursor widget
- `gui/dfstatus`: Can enable/disable individual categories and customize metal bar list
- `full-heal`: ``-r`` option removes corpses
- `gui/gm-editor`
- Pointers can now be displaced
- Added some useful aliases: "item" for the selected item, "screen" for the current screen, etc.
- Now avoids errors with unrecognized types
- `gui/hack-wish`: renamed to `gui/create-item`
- `keybinding list <keybinding>` accepts a context
- `lever`:
- Lists lever names
- ``lever pull`` can be used to pull the currently-selected lever
- ``memview``: Fixed display issue
- `modtools/create-item`: arguments are named more clearly, and you can specify the creator to be the unit with id ``df.global.unit_next_id-1`` (useful in conjunction with `modtools/create-unit`)
- ``nyan``: Can now be stopped with dfhack-run
- `plug`: lists all plugins; shows state and number of commands in plugins
- `prospect`: works from within command-prompt
- `quicksave`: Restricted to fortress mode
- `remotefortressreader`: Exposes more information
- `search-plugin`:
- Supports noble suggestion screen (e.g. suggesting a baron)
- Supports fortress mode loo[k] menu
- Recognizes ? and ; keys
- `stocks`: can now match beginning and end of item names
- `teleport`: Fixed cursor recognition
- `tidlers`, `twaterlvl`: now implemented by scripts instead of a plugin
- `tweak`:
- debug output now logged to stderr.log instead of console - makes DFHack start faster
- farm-plot-select: Fixed issues with selecting undiscovered crops
- `workflow`: Improved handling of plant reactions
Removed
-------
- `embark-tools` nano: 1x1 embarks are now possible in vanilla 0.40.24
DFHack 0.40.24-r3
=================
Internals
---------
- Ruby library now included on OS X - Ruby scripts should work on OS X 10.10
- libstdc++ should work with older versions of OS X
- Added support for `onMapLoad.init / onMapUnload.init <other_init_files>` scripts
- game type detection functions are now available in the World module
- The ``DFHACK_LOG_MEM_RANGES`` environment variable can be used to log information to ``stderr.log`` on OS X
- Fixed adventure mode menu names
- Fixed command usage information for some commands
Lua
---
- Lua scripts will only be reloaded if necessary
- Added a ``df2console()`` wrapper, useful for printing DF (CP437-encoded) text to the console in a portable way
- Added a ``strerror()`` wrapper
New Internal Commands
---------------------
- `hide`, `show`: hide and show the console on Windows
- `sc-script`: Allows additional scripts to be run when certain events occur (similar to `onLoad.init` scripts)
New Plugins
-----------
- `autohauler`: A hauling-only version of autolabor
New Scripts
-----------
- `modtools/reaction-product-trigger`: triggers callbacks when products are produced (contrast with when reactions complete)
New Tweaks
----------
- `fps-min <tweak>`: Fixes the in-game minimum FPS setting
- `shift-8-scroll <tweak>`: Gives Shift+8 (or ``*``) priority when scrolling menus, instead of scrolling the map
- `tradereq-pet-gender <tweak>`: Displays pet genders on the trade request screen
Fixes
-----
- Fixed game type detection in `3dveins`, `gui/create-item`, `reveal`, `seedwatch`
- ``PRELOAD_LIB``: More extensible on Linux
- `add-spatter`, `eventful`: Fixed crash on world load
- `add-thought`: Now has a proper subthought arg.
- `building-hacks`: Made buildings produce/consume correct amount of power
- `fix-armory`: compiles and is available again (albeit with issues)
- `gui/gm-editor`: Added search option (accessible with "s")
- `hack-wish <gui/create-item>`: Made items stack properly.
- `modtools/skill-change`: Made level granularity work properly.
- `show-unit-syndromes`: should work
- `stockflow`:
- Fixed error message in Arena mode
- no longer checks the DF version
- fixed ballistic arrow head orders
- convinces the bookkeeper to update records more often
- `zone`: Stopped crash when scrolling cage owner list
Misc Improvements
-----------------
- `autolabor`: A negative pool size can be specified to use the most unskilled dwarves
- `building-hacks`:
- Added a way to allow building to work even if it consumes more power than is available.
- Added setPower/getPower functions.
- `catsplosion`: Can now trigger pregnancies in (most) other creatures
- `exportlegends`: ``info`` and ``all`` options export ``legends_plus.xml`` with more data for legends utilities
- `manipulator`:
- Added ability to edit nicknames/profession names
- added "Job" as a View Type, in addition to "Profession" and "Squad"
- added custom profession templates with masking
- `remotefortressreader`: Exposes more information
DFHack 0.40.24-r2
=================
Internals
---------
- Lua scripts can set environment variables of each other with ``dfhack.run_script_with_env``
- Lua scripts can now call each others internal nonlocal functions with ``dfhack.script_environment(scriptName).functionName(arg1,arg2)``
- `eventful`: Lua reactions no longer require LUA_HOOK as a prefix; you can register a callback for the completion of any reaction with a name
- Filesystem module now provides file access/modification times and can list directories (normally and recursively)
- Units Module: New functions::
isWar
isHunter
isAvailableForAdoption
isOwnCiv
isOwnRace
getRaceName
getRaceNamePlural
getRaceBabyName
getRaceChildName
isBaby
isChild
isAdult
isEggLayer
isGrazer
isMilkable
isTrainableWar
isTrainableHunting
isTamable
isMale
isFemale
isMerchant
isForest
isMarkedForSlaughter
- Buildings Module: New Functions::
isActivityZone
isPenPasture
isPitPond
isActive
findPenPitAt
Fixes
-----
- ``dfhack.run_script`` should correctly find save-specific scripts now.
- `add-thought`: updated to properly affect stress.
- `hfs-pit`: should work now
- `autobutcher`: takes gelding into account
- :file:`init.lua` existence checks should be more reliable (notably when using non-English locales)
Misc Improvements
-----------------
Multiline commands are now possible inside dfhack.init scripts. See :file:`dfhack.init-example` for example usage.
DFHack 0.40.24-r1
=================
Internals
---------
CMake shouldn't cache DFHACK_RELEASE anymore. People may need to manually update/delete their CMake cache files to get rid of it.
DFHack 0.40.24-r0
=================
Internals
---------
- `EventManager`: fixed crash error with EQUIPMENT_CHANGE event.
- key modifier state exposed to Lua (ie :kbd:`Ctrl`, :kbd:`Alt`, :kbd:`Shift`)
Fixes
-----
``dfhack.sh`` can now be run from other directories on OS X
New Plugins
-----------
- `blueprint`: export part of your fortress to quickfort .csv files
New Scripts
-----------
- `hotkey-notes`: print key, name, and jump position of hotkeys
Removed
-------
- needs_porting/*
Misc Improvements
-----------------
Added support for searching more lists
Older Changelogs
================
Are kept in a seperate file: `HISTORY`
.. that's ``docs/history.rst``, if you're reading the raw text.

@ -0,0 +1,34 @@
{
"folders":
[
{
"path": "."
}
],
"build_systems":
[
{
"name": "DFHack make",
"working_dir": "$project_path",
"cmd": ["python", "$project_path/build/sublime/make.py", "$file"],
"variants": [
{
"name": "Build all",
"cmd": ["python", "$project_path/build/sublime/make.py", "-a"]
},
{
"name": "Build+install all",
"cmd": ["python", "$project_path/build/sublime/make.py", "-ai"]
},
{
"name": "Build plugin",
"cmd": ["python", "$project_path/build/sublime/make.py", "-ap", "$file"]
},
{
"name": "Build+install plugin",
"cmd": ["python", "$project_path/build/sublime/make.py", "-aip", "$file"]
}
]
}
]
}

@ -0,0 +1,63 @@
from __future__ import print_function
import argparse
import os
import re
import sys
import subprocess
class BuildError(Exception): pass
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?', default='', help='current filename')
parser.add_argument('-a', '--all', action='store_true', help='Build all targets')
parser.add_argument('-i', '--install', action='store_true', help='Install')
parser.add_argument('-p', '--plugin', action='store_true', help='Build specified plugin')
def find_build_folder():
# search for the one with the most recently modified Makefile
folder = None
mtime = 0
for f in os.listdir('.'):
if f.startswith('build'):
makefile = os.path.join(f, 'Makefile')
if os.path.isfile(makefile) and os.path.getmtime(makefile) > mtime:
folder = f
mtime = os.path.getmtime(makefile)
if not folder:
raise BuildError('No valid build folder found')
return folder
def get_plugin_name(filename):
filename = filename.replace('/devel', '')
match = re.search(r'plugins/(.+?)[/\.]', filename)
if match:
return match.group(1)
def run_command(cmd):
print('$ ' + ' '.join(cmd))
sys.stdout.flush()
if subprocess.call(cmd) != 0:
raise BuildError('command execution failed: ' + ' '.join(cmd))
def main(args):
os.chdir(find_build_folder())
print('Build folder:', os.getcwd())
cmd = ['make', '-j3']
if args.plugin:
plugin = get_plugin_name(args.file)
if not plugin:
raise BuildError('Cannot determine current plugin name from %r' % args.file)
cmd += [plugin + '/fast']
run_command(cmd)
if args.install:
run_command(['make', 'install/fast'])
if __name__ == '__main__':
try:
main(parser.parse_args())
except BuildError as e:
print('** Error: ' + str(e))
sys.exit(1)

@ -1,4 +1,4 @@
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
cd VC2015 cd VC2015
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj
cd .. cd ..

@ -1,4 +1,4 @@
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
cd VC2015 cd VC2015
msbuild /m /p:Platform=x64 /p:Configuration=Release ALL_BUILD.vcxproj msbuild /m /p:Platform=x64 /p:Configuration=Release ALL_BUILD.vcxproj
cd .. cd ..

@ -1,4 +1,4 @@
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
cd VC2015 cd VC2015
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo INSTALL.vcxproj msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo INSTALL.vcxproj
cd .. cd ..

@ -1,4 +1,4 @@
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 call "%ProgramFiles(x86)%\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
cd VC2015 cd VC2015
msbuild /m /p:Platform=x64 /p:Configuration=Release INSTALL.vcxproj msbuild /m /p:Platform=x64 /p:Configuration=Release INSTALL.vcxproj
cd .. cd ..

@ -21,6 +21,8 @@ import re
import shlex # pylint:disable=unused-import import shlex # pylint:disable=unused-import
import sys import sys
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs'))
from gen_changelog import generate_changelog
# -- Support :dfhack-keybind:`command` ------------------------------------ # -- Support :dfhack-keybind:`command` ------------------------------------
# this is a custom directive that pulls info from dfhack.init-example # this is a custom directive that pulls info from dfhack.init-example
@ -143,8 +145,6 @@ def write_script_docs():
magic strings. magic strings.
""" """
kinds = document_scripts() kinds = document_scripts()
if not os.path.isdir('docs/_auto'):
os.mkdir('docs/_auto')
head = { head = {
'base': 'Basic Scripts', 'base': 'Basic Scripts',
'devel': 'Development Scripts', 'devel': 'Development Scripts',
@ -179,6 +179,7 @@ def all_keybinds_documented():
# Actually call the docs generator and run test # Actually call the docs generator and run test
generate_changelog()
write_script_docs() write_script_docs()
all_keybinds_documented() all_keybinds_documented()
@ -264,6 +265,8 @@ exclude_patterns = [
'docs/html*', 'docs/html*',
'depends/*', 'depends/*',
'build*', 'build*',
'docs/_auto/news*',
'docs/_changelogs/',
] ]
# The reST default role (used for this markup: `text`) to use for all # The reST default role (used for this markup: `text`) to use for all
@ -288,7 +291,7 @@ html_style = 'dfhack.css'
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
html_theme_options = { html_theme_options = {
#'logo': 'logo.png', 'logo': 'dfhack-logo.png',
'github_user': 'DFHack', 'github_user': 'DFHack',
'github_repo': 'dfhack', 'github_repo': 'dfhack',
'github_button': False, 'github_button': False,

@ -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 EXCLUDE_FROM_ALL)
# 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)

@ -1 +1 @@
Subproject commit 0f0ad78c4fd429caacd0694b5c868dbeacea16b6 Subproject commit 6a9153d053a250be34996b3fd86ac1166c3e17cb

@ -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

@ -7,7 +7,7 @@ SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_
ADD_DEFINITIONS(-DLUA_COMPAT_BITLIB) ADD_DEFINITIONS(-DLUA_COMPAT_BITLIB)
IF(WIN32) IF(WIN32)
ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE /wd4334 )
ELSE() ELSE()
ADD_DEFINITIONS ( -DLUA_USE_POSIX -DLUA_USE_DLOPEN ) ADD_DEFINITIONS ( -DLUA_USE_POSIX -DLUA_USE_DLOPEN )
SET ( LIBS m dl ) SET ( LIBS m dl )

@ -1,3 +1,6 @@
project(dfhack-md5) project(dfhack-md5)
ADD_LIBRARY(dfhack-md5 STATIC EXCLUDE_FROM_ALL md5.cpp md5wrapper.cpp) ADD_LIBRARY(dfhack-md5 STATIC EXCLUDE_FROM_ALL md5.cpp md5wrapper.cpp)
IDE_FOLDER(dfhack-md5 "Depends") IDE_FOLDER(dfhack-md5 "Depends")
IF(UNIX)
SET_TARGET_PROPERTIES(dfhack-md5 PROPERTIES COMPILE_FLAGS "-Wno-strict-aliasing")
ENDIF()

@ -135,7 +135,7 @@ void MD5Final(unsigned char digest[16], MD5Context *ctx)
MD5Transform(ctx->buf, (uint32_t *) ctx->in); MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4); byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16); memcpy(digest, ctx->buf, 16);
memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */
} }

@ -16,10 +16,6 @@
* Petr Mrázek * Petr Mrázek
*/ */
#if defined(_MSC_VER) && _MSC_VER >= 1400
#define _CRT_SECURE_NO_WARNINGS
#endif
//---------------------------------------------------------------------- //----------------------------------------------------------------------
//basic includes //basic includes
#include <algorithm> #include <algorithm>

@ -1,57 +1,12 @@
PROJECT(protobuf) PROJECT(protobuf)
#Protocol buffers use C++0x hash maps, so we need to look for those. This is a rewrite of stl_hash.m4 in CMake. SET(HASH_MAP_H <unordered_map>)
IF(CMAKE_COMPILER_IS_GNUCC) SET(HASH_SET_H <unordered_map>)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") SET(HASH_NAMESPACE std)
ENDIF()
SET(HAVE_HASH_MAP 0)
SET(HASH_MAP_CLASS unordered_map) SET(HASH_MAP_CLASS unordered_map)
SET(HASH_SET_CLASS unordered_set)
#Check for all of the possible combinations of unordered_map and hash_map SET(HAVE_HASH_MAP 1)
SET(HAVE_HASH_SET 1)
FOREACH(header tr1/unordered_map unordered_map)
FOREACH(namespace std::tr1 std )
IF(HAVE_HASH_MAP EQUAL 0 AND NOT STL_HASH_OLD_GCC)
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
IF(CMAKE_CROSSCOMPILING)
TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
SET(HASH_MAP_RUN_RESULT ${HASH_MAP_COMPILE_RESULT})
ELSE()
TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
ENDIF()
IF (HASH_MAP_COMPILE_RESULT AND HASH_MAP_RUN_RESULT EQUAL 1)
SET(HASH_MAP_H <${header}>)
STRING(REPLACE "map" "set" HASH_SET_H ${HASH_MAP_H})
SET(HASH_NAMESPACE ${namespace})
SET(HASH_MAP_CLASS unordered_map)
SET(HASH_SET_CLASS unordered_set)
SET(HAVE_HASH_MAP 1)
SET(HAVE_HASH_SET 1)
ENDIF()
ENDIF()
ENDFOREACH(namespace)
ENDFOREACH(header)
IF (HAVE_HASH_MAP EQUAL 0)
SET(HASH_MAP_CLASS hash_map)
FOREACH(header ext/hash_map hash_map)
FOREACH(namespace __gnu_cxx "" std stdext)
IF (HAVE_HASH_MAP EQUAL 0)
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
IF (HASH_MAP_COMPILE_RESULT)
SET(HASH_MAP_H <${header}>)
STRING(REPLACE "map" "set" HASH_SET_H ${HASH_MAP_H})
SET(HASH_NAMESPACE ${namespace})
SET(HASH_MAP_CLASS hash_map)
SET(HASH_SET_CLASS hash_set)
SET(HAVE_HASH_MAP 1)
SET(HAVE_HASH_SET 1)
ENDIF()
ENDIF()
ENDFOREACH()
ENDFOREACH()
ENDIF()
IF (HAVE_HASH_MAP EQUAL 0) IF (HAVE_HASH_MAP EQUAL 0)
MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please install GCC >= 4.4, and all necessary 32-bit C++ development libraries.") MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please install GCC >= 4.4, and all necessary 32-bit C++ development libraries.")
@ -191,7 +146,7 @@ LIST(APPEND LIBPROTOBUF_FULL_SRCS ${LIBPROTOBUF_LITE_SRCS})
IF(CMAKE_COMPILER_IS_GNUCC) IF(CMAKE_COMPILER_IS_GNUCC)
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare") SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result -Wno-unused-local-typedefs -Wno-misleading-indentation")
ELSEIF(MSVC) ELSEIF(MSVC)
# Disable warnings for integer conversion to smaller type # Disable warnings for integer conversion to smaller type
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267")

@ -55,6 +55,12 @@ keybinding add Alt-S@dwarfmode/Default gui/settings-manager
# change quantity of manager orders # change quantity of manager orders
keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity
# view combat reports for the selected unit/corpse/spatter
keybinding add Ctrl-Shift-R view-unit-reports
# view extra unit information
keybinding add Alt-I@dwarfmode/ViewUnits|unitlist gui/unit-info-viewer
############################## ##############################
# Generic adv mode bindings # # Generic adv mode bindings #
############################## ##############################
@ -62,6 +68,8 @@ keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity
keybinding add Ctrl-B@dungeonmode adv-bodyswap keybinding add Ctrl-B@dungeonmode adv-bodyswap
keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force" keybinding add Ctrl-Shift-B@dungeonmode "adv-bodyswap force"
keybinding add Shift-O@dungeonmode gui/companion-order keybinding add Shift-O@dungeonmode gui/companion-order
keybinding add Ctrl-T@dungeonmode gui/advfort
keybinding add Ctrl-A@dungeonmode/ConversationSpeak adv-rumors
############################## ##############################
# Generic legends bindings # # Generic legends bindings #
@ -207,10 +215,11 @@ tweak civ-view-agreement
tweak eggs-fertile tweak eggs-fertile
tweak fps-min tweak fps-min
tweak hide-priority tweak hide-priority
tweak kitchen-keys tweak kitchen-prefs-all
tweak kitchen-prefs-empty tweak kitchen-prefs-empty
tweak max-wheelbarrow tweak max-wheelbarrow
tweak shift-8-scroll tweak shift-8-scroll
tweak stone-status-all
tweak title-start-rename tweak title-start-rename
tweak tradereq-pet-gender tweak tradereq-pet-gender

@ -19,17 +19,22 @@ 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
Caldfir caldfir Caldfir caldfir
Carter Bray Qartar Carter Bray Qartar
Chris Dombroski cdombroski Chris Dombroski cdombroski
Clayton Hughes Clayton Hughes
Clément Vuchener cvuchener Clément Vuchener cvuchener
Dan Amlund danamlund
Daniel Brooks db48x
David Corbett dscorbett David Corbett dscorbett
David Seguin dseguin David Seguin dseguin
David Timm dtimm
Deon Deon
DoctorVanGogh DoctorVanGogh DoctorVanGogh DoctorVanGogh
Donald Ruegsegger hashaash Donald Ruegsegger hashaash
@ -42,6 +47,7 @@ expwnent expwnent
Feng Feng
figment figment figment figment
gchristopher gchristopher gchristopher gchristopher
grubsteak grubsteak
Harlan Playford playfordh Harlan Playford playfordh
Hayati Ayguen hayguen Hayati Ayguen hayguen
IndigoFenix IndigoFenix
@ -53,8 +59,10 @@ 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
KlonZK KlonZK
Kris Parker kaypy Kris Parker kaypy
Kromtec Kromtec Kromtec Kromtec
Kurik Amudnil Kurik Amudnil
@ -118,13 +126,17 @@ Seth Woodworth sethwoodworth
simon simon
Simon Jackson sizeak Simon Jackson sizeak
stolencatkarma stolencatkarma
Stoyan Gaydarov sgayda2
suokko suokko shrieker
sv-esk sv-esk sv-esk sv-esk
Tacomagic Tacomagic
TheHologram TheHologram TheHologram TheHologram
ThiagoLira ThiagoLira
Tim Walberg twalberg Tim Walberg twalberg
Timothy Collett danaris Timothy Collett danaris
Tom Jobbins TheBloke Tom Jobbins TheBloke
Tom Prince Tom Prince
TotallyGatsby TotallyGatsby
Travis Hoppe thoppe orthographic-pedant Travis Hoppe thoppe orthographic-pedant
txtsd txtsd txtsd txtsd
U-glouglou\\simon U-glouglou\\simon

@ -33,13 +33,16 @@ To get the latest development code (develop branch), clone as above and then::
Generally, you should only need to clone DFHack once. Generally, you should only need to clone DFHack once.
**Important note regarding submodule update and changing branches**: **Important note regarding submodule update after pulling or changing branches**:
You must run ``git submodule update`` every time you change branches, You must run ``git submodule update`` every time you change branches, such as
such as when switching between the master and develop branches or vice versa. when switching between the master and develop branches or vice versa. You also
If a submodule only exists on the newer branch, you also need to run must run it after pulling any changes to submodules from the DFHack repo. If a
``git submodule update --init``. Failure to do this may result in strange submodule only exists on the newer branch, or if a commit you just pulled
build errors or "not a known DF version" errors. contains a new submodule, you need to run ``git submodule update --init``.
Failure to do this may result in a variety of errors, including ``fatal: <path>
does not exist`` when using Git, errors when building DFHack, and ``not a known
DF version`` when starting DF.
**More notes**: **More notes**:
@ -65,6 +68,24 @@ and more, please see `contributing-code`.
Build settings Build settings
============== ==============
Generator
---------
The ``Ninja`` CMake build generator is the prefered build method on Linux and
macOS, instead of ``Unix Makefiles``, which is the default. You can select Ninja
by passing ``-G Ninja`` to CMake. Incremental builds using Unix Makefiles can be
much slower than Ninja builds.
::
cmake .. -G Ninja
.. warning::
Most other CMake settings can be changed by running ``cmake`` again, but the
generator cannot be changed after ``cmake`` has been run without creating a
new build folder. Do not forget to specify this option.
Build type Build type
---------- ----------
@ -72,19 +93,16 @@ Build type
cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
Without specifying a build type or 'None', cmake uses the Valid and useful build types include 'Release' and 'RelWithDebInfo'. The default
``CMAKE_CXX_FLAGS`` variable for building. build type is 'Release'.
Valid and useful build types include 'Release', 'Debug' and
'RelWithDebInfo'.
'Debug' is not available on Windows; use 'RelWithDebInfo' instead.
Target architecture (32-bit vs. 64-bit) Target architecture (32-bit vs. 64-bit)
--------------------------------------- ---------------------------------------
Set DFHACK_BUILD_ARCH to either ``32`` or ``64`` to build a 32-bit or 64-bit Set DFHACK_BUILD_ARCH to either ``32`` or ``64`` to build a 32-bit or 64-bit
version of DFHack (respectively). The default is currently ``32``, but this may version of DFHack (respectively). The default is currently ``64``, so you will
change, so specifying it explicitly is a good idea. need to specify this explicitly for 32-bit builds. Specifying it is a good idea
in any case.
:: ::
@ -124,8 +142,9 @@ Before you can build anything, you'll also need ``cmake``. It is advisable to
also get ``ccmake`` on distributions that split the cmake package into multiple also get ``ccmake`` on distributions that split the cmake package into multiple
parts. parts.
You also need perl and the XML::LibXML and XML::LibXSLT perl packages (for the code generation parts). You also need zlib, libsdl (1.2, not sdl2, like DF), perl, and the XML::LibXML
You should be able to find them in your distro repositories. and XML::LibXSLT perl packages (for the code generation parts). You should be
able to find them in your distro repositories.
To build `stonesense`, you'll also need OpenGL headers. To build `stonesense`, you'll also need OpenGL headers.
@ -137,7 +156,7 @@ Here are some package install commands for various platforms:
* On Ubuntu:: * On Ubuntu::
apt-get install gcc cmake git zlib1g-dev libxml-libxml-perl libxml-libxslt-perl apt-get install gcc cmake ninja-build git zlib1g-dev libsdl1.2-dev libxml-libxml-perl libxml-libxslt-perl
* Debian and derived distros should have similar requirements to Ubuntu. * Debian and derived distros should have similar requirements to Ubuntu.
@ -170,8 +189,8 @@ Building is fairly straightforward. Enter the ``build`` folder (or create an
empty folder in the DFHack directory to use instead) and start the build like this:: empty folder in the DFHack directory to use instead) and start the build like this::
cd build cd build
cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=<path to DF> cmake .. -G Ninja -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=<path to DF>
make install # or make -jX install on multi-core systems to compile with X parallel processes ninja install # or ninja -jX install to specify the number of cores (X) to use
<path to DF> should be a path to a copy of Dwarf Fortress, of the appropriate <path to DF> should be a path to a copy of Dwarf Fortress, of the appropriate
version for the DFHack you are building. This will build the library along version for the DFHack you are building. This will build the library along
@ -180,8 +199,8 @@ with the normal set of plugins and install them into your DF folder.
Alternatively, you can use ccmake instead of cmake:: Alternatively, you can use ccmake instead of cmake::
cd build cd build
ccmake .. ccmake .. -G Ninja
make install ninja install
This will show a curses-based interface that lets you set all of the This will show a curses-based interface that lets you set all of the
extra options. You can also use a cmake-friendly IDE like KDevelop 4 extra options. You can also use a cmake-friendly IDE like KDevelop 4
@ -214,17 +233,17 @@ compilation-for-distribution with a GCC newer than 4.8.
Mac OS X Mac OS X
======== ========
DFHack functions similarly on OS X and Linux, and the majority of the DFHack functions similarly on OS X and Linux, and the majority of the
information above regarding the build process (cmake and make) applies here information above regarding the build process (cmake and ninja) applies here
as well. as well.
DFHack can officially be built on OS X with GCC 4.8. Anything newer than 4.8 DFHack can officially be built on OS X with GCC 4.8 or 7. Anything newer than 7
will require you to perform extra steps to get DFHack to run (see `osx-new-gcc-notes`), will require you to perform extra steps to get DFHack to run (see `osx-new-gcc-notes`),
and your build will likely not be redistributable. and your build will likely not be redistributable.
.. _osx-new-gcc-notes: .. _osx-new-gcc-notes:
Notes for GCC 4.9+ or OS X 10.10+ users Notes for GCC 8+ or OS X 10.10+ users
--------------------------------------- -------------------------------------
If none of these situations apply to you, skip to `osx-setup`. If none of these situations apply to you, skip to `osx-setup`.
@ -233,7 +252,7 @@ the following environment variable::
export MACOSX_DEPLOYMENT_TARGET=10.9 export MACOSX_DEPLOYMENT_TARGET=10.9
If you build with a GCC version newer than 4.8, DFHack will probably crash If you build with a GCC version newer than 7, DFHack will probably crash
immediately on startup, or soon after. To fix this, you will need to replace immediately on startup, or soon after. To fix this, you will need to replace
``hack/libstdc++.6.dylib`` with a symlink to the ``libstdc++.6.dylib`` included ``hack/libstdc++.6.dylib`` with a symlink to the ``libstdc++.6.dylib`` included
in your version of GCC:: in your version of GCC::
@ -241,16 +260,16 @@ in your version of GCC::
cd <path to df>/hack && mv libstdc++.6.dylib libstdc++.6.dylib.orig && cd <path to df>/hack && mv libstdc++.6.dylib libstdc++.6.dylib.orig &&
ln -s [PATH_TO_LIBSTDC++] . ln -s [PATH_TO_LIBSTDC++] .
For example, with GCC 5.2.0, ``PATH_TO_LIBSTDC++`` would be:: For example, with GCC 6.3.0, ``PATH_TO_LIBSTDC++`` would be::
/usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/libstdc++.6.dylib # for 64-bit DFHack /usr/local/Cellar/gcc@6/6.3.0/lib/gcc/6/libstdc++.6.dylib # for 64-bit DFHack
/usr/local/Cellar/gcc5/5.2.0/lib/gcc/5/i386/libstdc++.6.dylib # for 32-bit DFHack /usr/local/Cellar/gcc@6/6.3.0/lib/gcc/6/i386/libstdc++.6.dylib # for 32-bit DFHack
**Note:** If you build with a version of GCC that requires this, your DFHack **Note:** If you build with a version of GCC that requires this, your DFHack
build will *not* be redistributable. (Even if you copy the ``libstdc++.6.dylib`` build will *not* be redistributable. (Even if you copy the ``libstdc++.6.dylib``
from your GCC version and distribute that too, it will fail on older OS X from your GCC version and distribute that too, it will fail on older OS X
versions.) For this reason, if you plan on distributing DFHack, it is highly versions.) For this reason, if you plan on distributing DFHack, it is highly
recommended to use GCC 4.8. recommended to use GCC 4.8 or 7.
.. _osx-setup: .. _osx-setup:
@ -277,11 +296,12 @@ Dependencies and system set-up
brew tap homebrew/versions brew tap homebrew/versions
brew install git brew install git
brew install cmake brew install cmake
brew install gcc@4.8 brew install ninja
brew install gcc@7
Using `MacPorts <https://www.macports.org>`_:: Using `MacPorts <https://www.macports.org>`_::
sudo port install gcc48 +universal cmake +universal git-core +universal sudo port install gcc7 +universal cmake +universal git-core +universal ninja +universal
Macports will take some time - maybe hours. At some point it may ask Macports will take some time - maybe hours. At some point it may ask
you to install a Java environment; let it do so. you to install a Java environment; let it do so.
@ -323,22 +343,29 @@ Building
Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``):: Homebrew (if installed elsewhere, replace /usr/local with ``$(brew --prefix)``)::
export CC=/usr/local/bin/gcc-4.8 export CC=/usr/local/bin/gcc-7
export CXX=/usr/local/bin/g++-4.8 export CXX=/usr/local/bin/g++-7
Macports:: Macports::
export CC=/opt/local/bin/gcc-mp-4.8 export CC=/opt/local/bin/gcc-mp-7
export CXX=/opt/local/bin/g++-mp-4.8 export CXX=/opt/local/bin/g++-mp-7
Change the version numbers appropriately if you installed a different version of GCC. Change the version numbers appropriately if you installed a different version of GCC.
If you are confident that you have GCC in your path, you can omit the absolute paths::
export CC=gcc-7
export CXX=g++-7
etc.
* Build dfhack:: * Build dfhack::
mkdir build-osx mkdir build-osx
cd build-osx cd build-osx
cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=<path to DF> cmake .. -G Ninja -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=<path to DF>
make install # or make -j X install on multi-core systems to compile with X parallel processes ninja install # or ninja -jX install to specify the number of cores (X) to use
<path to DF> should be a path to a copy of Dwarf Fortress, of the appropriate <path to DF> should be a path to a copy of Dwarf Fortress, of the appropriate
version for the DFHack you are building. version for the DFHack you are building.
@ -353,7 +380,7 @@ Dependencies
------------ ------------
You will need the following: You will need the following:
* Microsoft Visual Studio 2015, with the C++ language * Microsoft Visual C++ 2015 or 2017
* Git * Git
* CMake * CMake
* Perl with XML::LibXML and XML::LibXSLT * Perl with XML::LibXML and XML::LibXSLT
@ -364,12 +391,16 @@ You will need the following:
Microsoft Visual Studio 2015 Microsoft Visual Studio 2015
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DFHack has to be compiled with the Microsoft Visual C++ 2015 toolchain; other DFHack has to be compiled with the Microsoft Visual C++ 2015 or 2017 toolchain on Windows;
versions won't work against Dwarf Fortress due to ABI and STL incompatibilities. other versions won't work against Dwarf Fortress due to ABI and STL incompatibilities.
You can install Visual Studio 2015_ or 2017_ Community edition for free, which
include all the features needed by DFHack. You can also download just the
`build tools`_ if you aren't going to use Visual Studio to edit code.
At present, the only way to obtain the MSVC C++ 2015 toolchain is to install a .. _2015: https://visualstudio.microsoft.com/vs/older-downloads/#visual-studio-2015-and-other-products
full copy of Microsoft Visual Studio 2015. The free Community version is .. _2017: https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15
sufficient. .. _build tools: https://visualstudio.microsoft.com/vs/older-downloads/#microsoft-build-tools-2015-update-3
Additional dependencies: installing with the Chocolatey Package Manager Additional dependencies: installing with the Chocolatey Package Manager
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -666,6 +697,20 @@ Then close that Admin ``cmd.exe``, re-open another Admin ``cmd.exe``, and run::
pip install sphinx pip install sphinx
.. _build-changelog:
Building the changelogs
-----------------------
If you have Python installed, but do not want to build all of the documentation,
you can build the changelogs with the ``docs/gen_changelog.py`` script.
All changes should be listed in ``changelog.txt``. A description of this file's
format follows:
.. include:: /docs/changelog.txt
:start-after: ===help
:end-before: ===end
Misc. Notes Misc. Notes
=========== ===========
@ -693,12 +738,19 @@ for. For example, if you are building for 32-bit Linux and 64-bit Windows,
download all files starting with ``linux32`` and ``win64``. GitHub should sort download all files starting with ``linux32`` and ``win64``. GitHub should sort
files alphabetically, so all the files you need should be next to each other. files alphabetically, so all the files you need should be next to each other.
.. note::
* Any files containing "allegro" in their filename are only necessary for
building `stonesense`. If you are not building Stonesense, you don't have to
download these, as they are larger than any other listed files.
It is recommended that you create a build folder and run CMake to verify that It is recommended that you create a build folder and run CMake to verify that
you have downloaded everything at this point, assuming your download machine has you have downloaded everything at this point, assuming your download machine has
CMake installed. This involves running a "generate" batch script on Windows, or CMake installed. This involves running a "generate" batch script on Windows, or
a command starting with ``cmake ..`` on Linux and OS X. CMake should a command starting with ``cmake .. -G Ninja`` on Linux and OS X, following the
automatically locate files that you placed in ``CMake/downloads``, and use them instructions in the sections above. CMake should automatically locate files that
instead of attempting to download them. you placed in ``CMake/downloads``, and use them instead of attempting to
download them.
.. _note-old-git-and-dfhack: .. _note-old-git-and-dfhack:

@ -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
@ -430,6 +435,45 @@ Other init files
directory, will be run when any world or that save is loaded. directory, will be run when any world or that save is loaded.
Environment variables
=====================
DFHack's behavior can be adjusted with some environment variables. For example,
on UNIX-like systems::
DFHACK_SOME_VAR=1 ./dfhack
- ``DFHACK_PORT``: the port to use for the RPC server (used by ``dfhack-run``
and `remotefortressreader` among others) instead of the default ``5000``. As
with the default, if this port cannot be used, the server is not started.
- ``DFHACK_DISABLE_CONSOLE``: if set, the DFHack console is not set up. This is
the default behavior if ``PRINT_MODE:TEXT`` is set in ``data/init/init.txt``.
Intended for situations where DFHack cannot run in a terminal window.
- ``DFHACK_HEADLESS``: if set, and ``PRINT_MODE:TEXT`` is set, DF's display will
be hidden, and the console will be started unless ``DFHACK_DISABLE_CONSOLE``
is also set. Intended for non-interactive gameplay only.
- ``DFHACK_NO_GLOBALS``, ``DFHACK_NO_VTABLES``: ignores all global or vtable
addresses in ``symbols.xml``, respectively. Intended for development use -
e.g. to make sure tools do not crash when these addresses are missing.
- ``DFHACK_NO_DEV_PLUGINS``: if set, any plugins from the plugins/devel folder
that are built and installed will not be loaded on startup.
- ``DFHACK_LOG_MEM_RANGES`` (macOS only): if set, logs memory ranges to
``stderr.log``. Note that `devel/lsmem` can also do this.
Other (non-DFHack-specific) variables that affect DFHack:
- ``TERM``: if this is set to ``dumb`` or ``cons25`` on \*nix, the console will
not support any escape sequences (arrow keys, etc.).
- ``LANG``, ``LC_CTYPE``: if either of these contain "UTF8" or "UTF-8" (not case
sensitive), ``DF2CONSOLE()`` will produce UTF-8-encoded text. Note that this
should be the case in most UTF-8-capable \*nix terminal emulators already.
Miscellaneous Notes Miscellaneous Notes
=================== ===================
This section is for odd but important notes that don't fit anywhere else. This section is for odd but important notes that don't fit anywhere else.

File diff suppressed because it is too large Load Diff

@ -804,6 +804,9 @@ Random number generation
C++ function wrappers C++ function wrappers
===================== =====================
.. contents::
:local:
Thin wrappers around C++ functions, similar to the ones for virtual methods. Thin wrappers around C++ functions, similar to the ones for virtual methods.
One notable difference is that these explicit wrappers allow argument count One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments adjustment according to the usual lua rules, so trailing false/nil arguments
@ -819,6 +822,7 @@ can be omitted.
* ``dfhack.getDFHackVersion()`` * ``dfhack.getDFHackVersion()``
* ``dfhack.getDFHackRelease()`` * ``dfhack.getDFHackRelease()``
* ``dfhack.getDFHackBuildID()``
* ``dfhack.getCompiledDFVersion()`` * ``dfhack.getCompiledDFVersion()``
* ``dfhack.getGitDescription()`` * ``dfhack.getGitDescription()``
* ``dfhack.getGitCommit()`` * ``dfhack.getGitCommit()``
@ -882,6 +886,9 @@ proper display on all platforms.
Gui module Gui module
---------- ----------
Screens
~~~~~~~
* ``dfhack.gui.getCurViewscreen([skip_dismissed])`` * ``dfhack.gui.getCurViewscreen([skip_dismissed])``
Returns the topmost viewscreen. If ``skip_dismissed`` is *true*, Returns the topmost viewscreen. If ``skip_dismissed`` is *true*,
@ -902,6 +909,9 @@ Gui module
the specified type (e.g. ``df.viewscreen_titlest``), or ``nil`` if none match. the specified type (e.g. ``df.viewscreen_titlest``), or ``nil`` if none match.
If ``depth`` is not specified or is less than 1, all viewscreens are checked. If ``depth`` is not specified or is less than 1, all viewscreens are checked.
General-purpose selections
~~~~~~~~~~~~~~~~~~~~~~~~~~
* ``dfhack.gui.getSelectedWorkshopJob([silent])`` * ``dfhack.gui.getSelectedWorkshopJob([silent])``
When a job is selected in :kbd:`q` mode, returns the job, else When a job is selected in :kbd:`q` mode, returns the job, else
@ -931,6 +941,53 @@ Gui module
Returns the plant selected via :kbd:`k`. Returns the plant selected via :kbd:`k`.
* ``dfhack.gui.getAnyUnit(screen)``
* ``dfhack.gui.getAnyItem(screen)``
* ``dfhack.gui.getAnyBuilding(screen)``
* ``dfhack.gui.getAnyPlant(screen)``
Similar to the corresponding ``getSelected`` functions, but operate on the
screen given instead of the current screen and always return ``nil`` silently
on failure.
Fortress mode
~~~~~~~~~~~~~
* ``dfhack.gui.getDwarfmodeViewDims()``
Returns dimensions of the main fortress mode screen. See ``getPanelLayout()``
in the ``gui.dwarfmode`` module for a more Lua-friendly version.
* ``dfhack.gui.resetDwarfmodeView([pause])``
Resets the fortress mode sidebar menus and cursors to their default state. If
``pause`` is true, also pauses the game.
* ``dfhack.gui.revealInDwarfmodeMap(pos)``
Centers the view on the given position, which can be a ``df.coord`` instance
or a table assignable to a ``df.coord`` (see `lua-api-table-assignment`),
e.g.::
{x = 5, y = 7, z = 11}
getSelectedUnit().pos
xyz2pos(pos2xyz(df.global.cursor))
Returns false if unsuccessful.
* ``dfhack.gui.refreshSidebar()``
Refreshes the fortress mode sidebar. This can be useful when making changes to
the map, for example, because DF only updates the sidebar when the cursor
position changes.
* ``dfhack.gui.inRenameBuilding()``
Returns ``true`` if a building is being renamed.
Announcements
~~~~~~~~~~~~~
* ``dfhack.gui.writeToGamelog(text)`` * ``dfhack.gui.writeToGamelog(text)``
Writes a string to :file:`gamelog.txt` without doing an announcement. Writes a string to :file:`gamelog.txt` without doing an announcement.
@ -975,6 +1032,13 @@ Gui module
Uses the type to look up options from announcements.txt, and calls the above Uses the type to look up options from announcements.txt, and calls the above
operations accordingly. The units are used to call ``addCombatReportAuto``. operations accordingly. The units are used to call ``addCombatReportAuto``.
Other
~~~~~
* ``dfhack.gui.getDepthAt(x, y)``
Returns the distance from the z-level of the tile at map coordinates (x, y) to
the closest ground z-level below. Defaults to 0, unless overriden by plugins.
Job module Job module
---------- ----------
@ -1120,14 +1184,27 @@ Units module
Finds (or creates if requested) a misc trait object with the given id. Finds (or creates if requested) a misc trait object with the given id.
* ``dfhack.units.isDead(unit)`` * ``dfhack.units.isActive(unit)``
The unit is completely dead and passive, or a ghost. The unit is active (alive and on the map).
* ``dfhack.units.isAlive(unit)`` * ``dfhack.units.isAlive(unit)``
The unit isn't dead or undead. The unit isn't dead or undead.
* ``dfhack.units.isDead(unit)``
The unit is completely dead and passive, or a ghost. Equivalent to
``dfhack.units.isKilled(unit) or dfhack.units.isGhost(unit)``.
* ``dfhack.units.isKilled(unit)``
The unit has been killed.
* ``dfhack.units.isGhost(unit)``
The unit is a ghost.
* ``dfhack.units.isSane(unit)`` * ``dfhack.units.isSane(unit)``
The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf. The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf.
@ -1195,6 +1272,18 @@ Units module
Retrieves the profession color for the given race/caste using raws. Retrieves the profession color for the given race/caste using raws.
* ``dfhack.units.getStressCategory(unit)``
Returns a number from 0-6 indicating stress. 0 is most stressed; 6 is least.
Note that 0 is guaranteed to remain the most stressed but 6 could change in the future.
* ``dfhack.units.getStressCategoryRaw(stress_level)``
Identical to ``getStressCategory`` but takes a raw stress level instead of a unit.
* ``dfhack.units.getStressCutoffs()``
Returns a table of the cutoffs used by the above stress level functions.
Items module Items module
------------ ------------
@ -1290,6 +1379,29 @@ Items module
Calculates the Basic Value of an item, as seen in the View Item screen. Calculates the Basic Value of an item, as seen in the View Item screen.
* ``dfhack.items.createItem(item_type, item_subtype, mat_type, mat_index, unit)``
Creates an item, similar to the `createitem` plugin.
* ``dfhack.items.checkMandates(item)``
Returns true if the item is free from mandates, or false if mandates prevent trading the item.
* ``dfhack.items.canTrade(item)``
Checks whether the item can be traded.
* ``dfhack.items.canTradeWithContents(item)``
Checks whether the item and all items it contains, if any, can be traded.
* ``dfhack.items.isRouteVehicle(item)``
Checks whether the item is an assigned hauling vehicle.
* ``dfhack.items.isSquadEquipment(item)``
Checks whether the item is assigned to a squad.
Maps module Maps module
----------- -----------
@ -1548,6 +1660,13 @@ Low-level building creation functions:
Returns *true* if the building is marked for removal (with :kbd:`x`), *false* Returns *true* if the building is marked for removal (with :kbd:`x`), *false*
otherwise. otherwise.
* ``dfhack.buildings.getRoomDescription(building[, unit])``
If the building is a room, returns a description including quality modifiers, e.g. "Royal Bedroom".
Otherwise, returns an empty string.
The unit argument is passed through to DF and may modify the room's value depending on the unit given.
High-level High-level
~~~~~~~~~~ ~~~~~~~~~~
More high-level functions are implemented in lua and can be loaded by More high-level functions are implemented in lua and can be loaded by
@ -1645,6 +1764,27 @@ Constructions module
Returns *true, was_only_planned* if removed; or *false* if none found. Returns *true, was_only_planned* if removed; or *false* if none found.
Kitchen module
--------------
* ``dfhack.kitchen.findExclusion(type, item_type, item_subtype, mat_type, mat_index)``
Finds a kitchen exclusion in the vectors in ``df.global.ui.kitchen``. Returns
-1 if not found.
* ``type`` is a ``df.kitchen_exc_type``, i.e. ``df.kitchen_exc_type.Cook`` or
``df.kitchen_exc_type.Brew``.
* ``item_type`` is a ``df.item_type``
* ``item_subtype``, ``mat_type``, and ``mat_index`` are all numeric
* ``dfhack.kitchen.addExclusion(type, item_type, item_subtype, mat_type, mat_index)``
* ``dfhack.kitchen.removeExclusion(type, item_type, item_subtype, mat_type, mat_index)``
Adds or removes a kitchen exclusion, using the same parameters as
``findExclusion``. Both return ``true`` on success and ``false`` on failure,
e.g. when adding an exclusion that already exists or removing one that does
not.
Screen API Screen API
---------- ----------
@ -2277,6 +2417,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.
@ -2492,6 +2636,61 @@ function:
argument specifies the indentation step size in spaces. For argument specifies the indentation step size in spaces. For
the other arguments see the original documentation link above. the other arguments see the original documentation link above.
profiler
========
A third-party lua profiler module from
http://lua-users.org/wiki/PepperfishProfiler. Module defines one function to
create profiler objects which can be used to profile and generate report.
* ``profiler.newProfiler([variant[, sampling_frequency]])``
Returns an profile object with ``variant`` either ``'time'`` or ``'call'``.
``'time'`` variant takes optional ``sampling_frequency`` parameter to select
lua instruction counts between samples. Default is ``'time'`` variant with
``10*1000`` frequency.
``'call'`` variant has much higher runtime cost which will increase the
runtime of profiled code by factor of ten. For the extreme costs it provides
accurate function call counts that can help locate code which takes much time
in native calls.
* ``obj:start()``
Resets collected statistics. Then it starts collecting new statistics.
* ``obj:stop()``
Stops profile collection.
* ``obj:report(outfile[, sort_by_total_time])``
Write a report from previous statistics collection to ``outfile``.
``outfile`` should be writeable io file object (``io.open`` or
``io.stdout``). Passing ``true`` as second parameter ``sort_by_total_time``
switches sorting order to use total time instead of default self time order.
* ``obj:prevent(function)``
Adds an ignore filter for a ``function``. It will ignore the pointed function
and all of it children.
Examples
--------
::
local prof = profiler.newProfiler()
prof:start()
profiledCode()
prof:stop()
local out = io.open( "lua-profile.txt", "w+")
prof:report(out)
out:close()
class class
===== =====
@ -3335,6 +3534,8 @@ The widget implements:
Same as with an ordinary list. Same as with an ordinary list.
.. _lua-plugins:
======= =======
Plugins Plugins
======= =======
@ -3349,6 +3550,19 @@ module file is still necessary for ``require`` to read.
The following plugins have lua support. The following plugins have lua support.
blueprint
=========
Native functions:
* ``dig(start, end, name)``
* ``build(start, end, name)``
* ``place(start, end, name)``
* ``query(start, end, name)``
``start`` and ``end`` are tables containing positions (see
``xyz2pos``). ``name`` is used as the basis for the filename.
burrows burrows
======= =======
@ -3706,6 +3920,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
@ -3777,6 +4112,12 @@ Note that this function lets errors propagate to the caller.
This is intended to only allow scripts that take appropriate action when used This is intended to only allow scripts that take appropriate action when used
as a module to be loaded. as a module to be loaded.
* ``dfhack.script_help([name, [extension]])``
Returns the contents of the embedded documentation of the specified script.
``extension`` defaults to "lua", and ``name`` defaults to the name of the
script where this function was called.
Enabling and disabling scripts Enabling and disabling scripts
============================== ==============================

@ -1,32 +1,6 @@
.. comment .. comment
This is the development changelog file for DFHack. If you add or change This is the changelog for development releases. Entries are included from
anything, note it here under the heading "DFHack Future", in the appropriate changelog.txt.
section. Items within each section are listed in alphabetical order to
minimise merge conflicts. Try to match the style and level of detail of the
other entries.
This file contains changes that are relevant to users of prereleases. These
changes should include changes from just the previous release, whether that
release was stable or not. For instance, a feature added in 0.43.05-alpha1
should go under "0.43.05-alpha1" here *and* "0.43.05-r1" (or "future") in
NEWS.rst. A fix in one prerelease for an issue in the previous prerelease
should just go here in the appropriate section, not in NEWS.rst.
Sections for each release are added as required, and consist solely of the
following in order as subheadings::
Fixes
Structures
API Changes
Additions/Removals
Other Changes
When referring to a script, plugin, or command, use backticks (```) to
create a link to the relevant documentation - and check that the docs are
still up to date!
When adding a new release, change "DFHack future" to the appropriate title
before releasing, and then add a new "DFHack future" section after releasing.
.. _dev-changelog: .. _dev-changelog:
@ -34,287 +8,12 @@
Development Changelog Development Changelog
##################### #####################
.. contents:: This file contains changes grouped by the release (stable or development) in
:depth: 2 which they first appeared. See `build-changelog` for more information.
DFHack 0.44.03-beta1
====================
Fixes
-----
- `autolabor`, `autohauler`, `labormanager`: added support for "put item on
display" jobs and building/destroying display furniture
- `gui/gm-editor`: fixed an error when editing primitives in Lua tables
Structures
----------
- Added 7 new globals from DF: ``version``, ``min_load_version``,
``movie_version``, ``basic_seed``, ``title``, ``title_spaced``,
``ui_building_resize_radius``
- Added ``twbt_render_map`` code offset on x64
- Fixed an issue preventing ``enabler`` from being allocated by DFHack
- Added ``job_type.PutItemOnDisplay``
- Found ``renderer`` vtable on osx64
- ``adventure_movement_optionst``, ``adventure_movement_hold_tilest``,
``adventure_movement_climbst``: named coordinate fields
- ``mission``: added type
- ``unit``: added 3 new vmethods: ``getCreatureTile``, ``getCorpseTile``, ``getGlowTile``
- ``viewscreen_assign_display_itemst``: fixed layout on x64 and identified many fields
- ``viewscreen_reportlistst``: fixed layout, added ``mission_id`` vector
- ``world.status``: named ``missions`` vector
Other Changes
-------------
- `devel/dump-offsets`: now ignores ``index`` globals
- `gui/pathable`: added tile types to sidebar
- `modtools/skill-change`:
- now updates skill levels appropriately
- only prints output if ``-loud`` is passed
DFHack 0.44.03-alpha1
=====================
Other Changes
-------------
- Lua: Improved ``json`` I/O error messages
- Lua: Stopped a crash when trying to create instances of classes whose vtable
addresses are not available
DFHack 0.44.02-beta1
====================
Fixes
-----
- Fixed issues with the console output color affecting the prompt on Windows
- `createitem`: stopped items from teleporting away in some forts
- `gui/gm-unit`: can now edit mining skill
- `gui/quickcmd`: stopped error from adding too many commands
- `modtools/create-unit`: fixed error when domesticating units
Structures
----------
- Located ``start_dwarf_count`` offset for all builds except 64-bit Linux;
`startdwarf` should work now
- Added ``buildings_other_id.DISPLAY_CASE``
- Fixed ``viewscreen_titlest.start_savegames`` alignment
- Fixed ``unit`` alignment
- Identified ``historical_entity.unknown1b.deities`` (deity IDs)
API Changes
-----------
- Lua; Exposed ``get_vector()`` (from C++) for all types that support
``find()``, e.g. ``df.unit.get_vector() == df.global.world.units.all``
Additions/Removals
------------------
- Added `devel/check-other-ids`: Checks the validity of "other" vectors in the
``world`` global
- Added `gui/cp437-table`: An in-game CP437 table
- Removed `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01
Other Changes
-------------
- The console now provides suggestions for built-in commands
- `devel/export-dt-ini`: avoid hardcoding flags
- `gui/gm-editor`: added enum names to enum edit dialogs
- `gui/gm-unit`: made skill search case-insensitive
- `gui/rename`: added "clear" and "special characters" options
- `remotefortressreader`: includes item stack sizes and some performance improvements
DFHack 0.44.02-alpha1
=====================
Fixes
-----
- Fixed a crash that could occur if a symbol table in symbols.xml had no content
- The Lua API can now wrap functions with 12 or 13 parameters
Structures
----------
- The ``ui_menu_width`` global is now a 2-byte array; the second item is the
former ``ui_area_map_width`` global, which is now removed
- The former ``announcements`` global is now a field in ``d_init``
- ``world`` fields formerly beginning with ``job_`` are now fields of
``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list``
API Changes
-----------
- Lua: Added a new ``dfhack.console`` API
DFHack 0.43.05-beta2
====================
Fixes See `changelog` for a list of changes grouped by stable releases.
-----
- Fixed Buildings::updateBuildings(), along with building creation/deletion events
- Fixed ``plug`` output alignment for plugins with long names
- Fixed a crash that happened when a ``LUA_PATH`` environment variable was set
- `add-thought`: fixed number conversion
- `gui/workflow`: fixed range editing producing the wrong results for certain numbers
- `modtools/create-unit`: now uses non-English names
- `modtools/item-trigger`: fixed errors with plant growths
- `remotefortressreader`: fixed a crash when serializing the local map
- `stockflow`: fixed an issue with non-integer manager order limits
- `title-folder`: fixed compatibility issues with certain SDL libraries on macOS
Structures .. contents::
---------- :depth: 2
- Added some missing renderer VTable addresses on macOS
- ``entity.resources.organic``: identified ``parchment``
- ``entity_sell_category``: added ``Parchment`` and ``CupsMugsGoblets``
- ``ui_advmode_menu``: added ``Build``
- ``ui_unit_view_mode``: added ``PrefOccupation``
- ``unit_skill``: identified ``natural_skill_lvl`` (was ``unk_1c``)
- ``viewscreen_jobmanagementst``: identified ``max_workshops``
- ``viewscreen_overallstatusst``: made ``visible_pages`` an enum
- ``viewscreen_pricest``: identified fields
- ``viewscreen_workquota_conditionst``: gave some fields ``unk`` names
API Changes
-----------
- Allowed the Lua API to accept integer-like floats and strings when expecting an integer
- Lua: New ``Painter:key_string()`` method
- Lua: Added ``dfhack.getArchitecture()`` and ``dfhack.getArchitectureName()``
Additions/Removals:
-------------------
- Added `adv-rumors` script: improves the "Bring up specific incident or rumor" menu in adventure mode
- Added `install-info` script for basic troubleshooting
- Added `tweak condition-material <tweak>`: fixes a crash in the work order condition material list
- Added `tweak hotkey-clear <tweak>`: adds an option to clear bindings from DF hotkeys
- `autofarm`: reverted local biome detection (from 0.43.05-alpha3)
Other Changes
-------------
- Added a DOWNLOAD_RUBY CMake option, to allow use of a system/external ruby library
- Added the ability to download files manually before building
- `gui/extended-status`: added a feature to queue beds
- `remotefortressreader`: added building items, DF version info
- `stonesense`: Added support for 64-bit macOS and Linux
DFHack 0.43.05-beta1
====================
Fixes
-----
- Fixed various crashes on 64-bit Windows related to DFHack screens, notably `manipulator`
- Fixed addresses of next_id globals on 64-bit Linux (fixes an `automaterial`/box-select crash)
- ``ls`` now lists scripts in folders other than ``hack/scripts``, when applicable
- `modtools/create-unit`: stopped permanently overwriting the creature creation
menu in arena mode
- `season-palette`: fixed an issue where only part of the screen was redrawn
after changing the color scheme
- `title-version`: now hidden when loading an arena
Structures
----------
- ``file_compressorst``: fixed field sizes on x64
- ``historical_entity``: fixed alignment on x64
- ``ui_sidebar_menus.command_line``: fixed field sizes on x64
- ``viewscreen_choose_start_sitest``: added 3 missing fields, renamed ``in_embark_only_warning``
- ``viewscreen_layer_arena_creaturest``: identified more fields
- ``world.math``: identified
- ``world.murky_pools``: identified
Additions/Removals
------------------
- `generated-creature-renamer`: Renames generated creature IDs for use with graphics packs
Other Changes
-------------
- `title-version`: Added a prerelease indicator
DFHack 0.43.05-alpha4
=====================
Fixes
-----
- Fixed an issue with uninitialized bitfields that was causing several issues
(disappearing buildings in `buildingplan`'s planning mode, strange behavior in
the extended `stocks` screen, and likely other problems). This issue was
introduced in 0.43.05-alpha3.
- `stockflow`: Fixed an "integer expected" error
Structures
----------
- Located several globals on 64-bit Linux: flows, timed_events, ui_advmode,
ui_building_assign_type, ui_building_assign_is_marked,
ui_building_assign_units, ui_building_assign_items, and ui_look_list. This
fixes `search-plugin`, `zone`, and `force`, among others.
- ``ui_sidebar_menus``: Fixed some x64 alignment issues
Additions/Removals
------------------
- Added `fix/tile-occupancy`: Clears bad occupancy flags on the selected tile.
Useful for fixing blocked tiles introduced by the above buildingplan issue.
- Added a Lua ``tile-material`` module
Other Changes
-------------
- `labormanager`: Add support for shell crafts
- `manipulator`: Custom professions are now sorted alphabetically more reliably
DFHack 0.43.05-alpha3
=====================
Fixes
-----
- `add-thought`: fixed support for emotion names
- `autofarm`: Made surface farms detect local biome
- `devel/export-dt-ini`: fixed squad_schedule_entry size
- `labormanager`:
- Now accounts for unit attributes
- Made instrument-building jobs work (constructed instruments)
- Fixed deconstructing constructed instruments
- Fixed jobs in bowyer's shops
- Fixed trap component jobs
- Fixed multi-material construction jobs
- Fixed deconstruction of buildings containing items
- Fixed interference caused by "store item in vehicle" jobs
- `manipulator`: Fixed crash when selecting a profession from an empty list
- `ruby`:
- Fixed crash on Win64 due to truncated global addresses
- Fixed compilation on Win64
- Use correct raw string length with encodings
Structures
----------
- Changed many ``comment`` XML attributes with version numbers to use new
``since`` attribute instead
- ``activity_event_conflictst.sides``: named many fields
- ``building_def.build_key``: fixed size on 64-bit Linux and OS X
- ``historical_kills``:
- ``unk_30`` -> ``killed_underground_region``
- ``unk_40`` -> ``killed_region``
- ``historical_kills.killed_undead``: removed ``skeletal`` flag
- ``ui_advmode``: aligned enough so that it doesn't crash (64-bit OS X/Linux)
- ``ui_advmode.show_menu``: changed from bool to enum
- ``unit_personality.emotions.flags``: now a bitfield
API Changes
-----------
- Added ``DFHack::Job::removeJob()`` function
- C++: Removed bitfield constructors that take an initial value. These kept
bitfields from being used in unions. Set ``bitfield.whole`` directly instead.
- Lua: ``bitfield.whole`` now returns an integer, not a decimal
Additions/Removals
------------------
- Removed source for treefarm plugin (wasn't built)
- Added `modtools/change-build-menu`: Edit the build mode sidebar menus
- Added `modtools/if-entity`: Run a command if the current entity matches a
given ID
- Added `season-palette`: Swap color palettes with the changes of the seasons
Other changes .. include:: /docs/_auto/news-dev.rst
-------------
- Changed minimum GCC version to 4.8 on OS X and Linux (earlier versions
wouldn't have worked on Linux anyway)
- Updated TinyXML from 2.5.3 to 2.6.2

@ -0,0 +1,26 @@
.. comment
This is the changelog for stable releases. Entries are included from
changelog.txt.
.. _changelog:
#########
Changelog
#########
This file contains changes grouped by the stable release in which they first
appeared. See `build-changelog` for more information.
See `dev-changelog` for a list of changes grouped by development releases.
.. contents::
:depth: 2
.. include:: /docs/_auto/news.rst
Older Changelogs
================
Are kept in a seperate file: `HISTORY`
.. that's ``docs/history.rst``, if you're reading the raw text.

@ -129,6 +129,7 @@ probe
Can be used to determine tile properties like temperature. Can be used to determine tile properties like temperature.
.. _prospect: .. _prospect:
.. _prospector:
prospect prospect
======== ========
@ -311,7 +312,7 @@ Subcommands that persist until disabled or DF quits:
:import-priority-category: :import-priority-category:
Allows changing the priority of all goods in a Allows changing the priority of all goods in a
category when discussing an import agreement with the liaison category when discussing an import agreement with the liaison
:kitchen-keys: Fixes DF kitchen meal keybindings (:bug:`614`) :kitchen-prefs-all: Adds an option to toggle cook/brew for all visible items in kitchen preferences
:kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences :kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences
:kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) :kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`)
:max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile :max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile
@ -328,6 +329,7 @@ Subcommands that persist until disabled or DF quits:
:nestbox-color: Fixes the color of built nestboxes :nestbox-color: Fixes the color of built nestboxes
:shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map :shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map
:stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode. :stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode.
:stone-status-all: Adds an option to toggle the economic status of all stones
:title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu :title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu
:tradereq-pet-gender: Displays pet genders on the trade request screen :tradereq-pet-gender: Displays pet genders on the trade request screen
@ -527,6 +529,18 @@ nopause
Disables pausing (both manual and automatic) with the exception of pause forced Disables pausing (both manual and automatic) with the exception of pause forced
by `reveal` ``hell``. This is nice for digging under rivers. by `reveal` ``hell``. This is nice for digging under rivers.
.. _embark-assistant:
embark-assistant
================
This plugin provides embark site selection help. It has to be run with the
``embark-assistant`` command while the pre-embark screen is displayed and shows
extended (and correct(?)) resource information for the embark rectangle as well
as normally undisplayed sites in the current embark region. It also has a site
selection tool with more options than DF's vanilla search tool. For detailed
help invoke the in game info screen.
.. _embark-tools: .. _embark-tools:
embark-tools embark-tools
@ -710,8 +724,8 @@ Replaces the DF stocks screen with an improved version.
.. _stocksettings: .. _stocksettings:
.. _stockpiles: .. _stockpiles:
stocksettings stockpiles
============= ==========
Offers the following commands to save and load stockpile settings. Offers the following commands to save and load stockpile settings.
See `gui/stockpiles` for an in-game interface. See `gui/stockpiles` for an in-game interface.
@ -1046,6 +1060,9 @@ autogems
Creates a new Workshop Order setting, automatically cutting rough gems Creates a new Workshop Order setting, automatically cutting rough gems
when `enabled <enable>`. when `enabled <enable>`.
See `gui/autogems` for a configuration UI. If necessary, the ``autogems-reload``
command reloads the configuration file produced by that script.
.. _stockflow: .. _stockflow:
stockflow stockflow
@ -1916,6 +1933,10 @@ all 'down ramps' that can remain after a cave-in (you don't have to designate
anything for that to happen). anything for that to happen).
.. _dig: .. _dig:
.. _digv:
.. _digvx:
.. _digl:
.. _diglx:
dig dig
=== ===
@ -1931,6 +1952,13 @@ Basic commands:
:dfhack-keybind:`digv` :dfhack-keybind:`digv`
.. note::
All commands implemented by the `dig` plugin (listed by ``ls dig``) support
specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``,
where ``#`` is a number from 1 to 7. If a priority is not specified, the
priority selected in-game is used as the default.
.. _digexp: .. _digexp:
digexp digexp
@ -2707,3 +2735,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`

@ -0,0 +1 @@
*.rst

@ -0,0 +1 @@
*.txt

@ -0,0 +1,692 @@
=== Scroll down for changes
===[[[
===help
Entries in docs/NEWS.rst and docs/NEWS-dev.rst are generated from
docs/changelog.txt. NEWS.rst groups entries by stable releases, and NEWS-dev.rst
groups them by all releases (stable and development). For example, an entry
listed under "0.44.05-alpha1" in changelog.txt will be listed under that in
NEWS-dev.rst as well, but under "0.44.05-r1" in NEWS.rst (assuming that is the
closest stable release after 0.44.05-alpha1). An entry listed under a stable
release in changelog.txt will be listed under that release in both NEWS.rst and
NEWS-dev.rst.
changelog.txt uses a syntax similar to RST, with a few special sequences:
- ``===`` indicates the start of a comment
- ``#`` indicates the start of a release name (do not include "DFHack")
- ``##`` indicates the start of a section name (this must be listed in ``gen_changelog.py``)
- ``-`` indicates the start of a changelog entry. **Note:** an entry currently must be only one line.
- ``:`` (colon followed by space) separates the name of a feature from a description of a change to that feature.
Changes made to the same feature are grouped if they end up in the same section.
- ``:\`` (colon, backslash, space) avoids the above behavior
- ``- @`` (the space is optional) indicates the start of an entry that should only be displayed in NEWS-dev.rst.
Use this sparingly, e.g. for immediate fixes to one development build in another development build that
are not of interest to users of stable builds only.
- Three ``[`` characters indicate the start of a block (possibly a comment) that
spans multiple lines. Three ``]`` characters indicate the end of such a block.
- ``!`` immediately before a phrase set up to be replaced (see gen_changelog.py) stops that occurrence from being replaced.
===end
]]]
================================================================================
======== IMPORTANT: rename this, and add a new "future" section, BEFORE ========
======== making a new DFHack release! ========
================================================================================
# Future
## Fixes
- `building-hacks`: fixed error when dealing with custom animation tables
- `devel/test-perlin`: fixed Lua error (``math.pow()``)
- `embark-assistant`: fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices
- `labormanager`:
- stopped assigning labors to ineligible dwarves, pets, etc.
- stopped assigning invalid labors
- added support for crafting jobs that use pearl
- `prospector`: (also affected `embark-tools`) - fixed a crash when prospecting an unusable site (ocean, mountains, etc.) with a large default embark size in d_init.txt (e.g. 16x16)
- `siege-engine`: fixed a few Lua errors (``math.pow()``, ``unit.relationship_ids``)
## Misc Improvements
- `devel/export-dt-ini`: added viewscreen offsets for DT 40.1.2
- `labormanager`: now takes nature value into account when assigning jobs
## Internals
- Linux/macOS: changed recommended build backend from Make to Ninja (Make builds will be significantly slower now)
- Added a usable unit test framework for basic tests, and a few basic tests
- Core: various thread safety and memory management improvements
- Fixed cmake build dependencies for generated header files
- Fixed custom ``CMAKE_CXX_FLAGS`` not being passed to plugins
- Changed ``plugins/CMakeLists.custom.txt`` to be ignored by git and created (if needed) at build time instead
## Lua
- ``utils``: new ``OrderedTable`` class
================================================================================
# 0.44.12-r1
## Fixes
-@ Console: fixed crash when entering long commands on Linux/macOS
-@ Removed jsoncpp's ``include`` and ``lib`` folders from DFHack builds/packages
- Fixed special characters in `command-prompt` and other non-console in-game outputs on Linux/macOS (in tools using ``df2console``)
- `die`: fixed Windows crash in exit handling
- `dwarfmonitor`, `manipulator`: fixed stress cutoffs
- `modtools/force`: fixed a bug where the help text would always be displayed and nothing useful would happen
- `ruby`: fixed calling conventions for vmethods that return strings (currently ``enabler.GetKeyDisplay()``)
- `startdwarf`: fixed on 64-bit Linux
## Misc Improvements
- Reduced time for designation jobs from tools like `digv` to be assigned workers
- `embark-assistant`:
- Switched to standard scrolling keys, improved spacing slightly
- Introduced scrolling of Finder search criteria, removing requirement for 46 lines to work properly (Help/Info still formatted for 46 lines).
- Added Freezing search criterion, allowing searches for NA/Frozen/At_Least_Partial/Partial/At_Most_Partial/Never Freezing embarks.
- `rejuvenate`:
- Added ``-all`` argument to apply to all citizens
- Added ``-force`` to include units under 20 years old
- Clarified documentation
## API
- Added to ``Units`` module:
- ``getStressCategory(unit)``
- ``getStressCategoryRaw(level)``
- ``stress_cutoffs`` (Lua: ``getStressCutoffs()``)
## Internals
- Changed default build architecture to 64-bit
- Added documentation for all RPC functions and a build-time check
- Added support for build IDs to development builds
- Use ``dlsym(3)`` to find vtables from libgraphics.so
## Structures
- Added ``start_dwarf_count`` on 64-bit Linux again and fixed scanning script
- ``army_controller``: added new vector from 0.44.11
-@ ``viewscreen_civlistst``: split ``unk_20`` into 3 pointers
- ``belief_system``: new type, few fields identified
- ``mental_picture``: new type, some fields identified
- ``mission``: new type (used in ``viewscreen_civlistst``)
- ``mission_report``:
- new type (renamed, was ``mission`` before)
- identified some fields
- ``spoils_report``: new type, most fields identified
- ``viewscreen_civlistst``:
- identified new pages
- identified new messenger-related fields
- ``viewscreen_image_creatorst``:
- fixed layout
- identified many fields
- ``viewscreen_reportlistst``:
- added new mission and spoils report-related fields (fixed layout)
- ``world``:
- ``belief_systems``: identified
- ``world.languages``: identified (minimal information; whole languages stored elsewhere)
- ``world.status``:
- ``mission_reports``: renamed, was ``missions``
- ``spoils_reports``: identified
- ``world.unk_131ec0``, ``world.unk_131ef0``: researched layout
- ``world.worldgen_status``: identified many fields
================================================================================
# 0.44.12-alpha1
## Fixes
-@ macOS: fixed ``renderer`` vtable address on x64 (fixes `rendermax`)
- `stonesense`: fixed ``PLANT:DESERT_LIME:LEAF`` typo
## API
- Added C++-style linked list interface for DF linked lists
## Structures
- Dropped 0.44.11 support
- ``ui.squads``: Added fields new in 0.44.12
================================================================================
# 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
- 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 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
- `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`:
- stop running repeatedly when paused
- 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
- `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
- `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
- 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
- Linux: Added several new options to ``dfhack`` script: ``--remotegdb``, ``--gdbserver``, ``--strace``
- `bodyswap`: improved error handling
- `buildingplan`: added max quality setting
- `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`
- `item-descriptions`: fixed several grammatical errors
- `modtools/create-unit`:
- added quantity argument
- now selects a caste at random if none is specified
- `mousequery`:
- migrated several features from TWBT's fork
- added ability to drag with left/right buttons
- added depth display for TWBT (when multilevel is enabled)
- made shift+click jump to lower levels visible with TWBT
- `title-version`: added version to options screen too
- `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
- New functions (also exposed to Lua):
- ``Units::isKilled()``
- ``Units::isActive()``
- ``Units::isGhost()``
- Removed Vermin module (unused and obsolete)
## Lua
- Added ``profiler`` module to measure lua performance
- 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
- Added fallback for YouCompleteMe database lookup failures (e.g. for newly-created files)
- 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
## New Scripts
- `bodyswap`: shifts player control over to another unit in adventure mode
## New Tweaks
- `tweak` stone-status-all: adds an option to toggle the economic status of all stones
- `tweak` kitchen-prefs-all: adds an option to toggle cook/brew for all visible items in kitchen preferences
## Fixes
- Lua: registered ``dfhack.constructions.designateRemove()`` correctly
- `prospector`: fixed crash due to invalid vein materials
- `tweak` max-wheelbarrow: fixed conflict with building renaming
- `view-item-info`: stopped appending extra newlines permanently to descriptions
## Misc Improvements
- Added logo to documentation
- Documented several missing ``dfhack.gui`` Lua functions
- `adv-rumors`: bound to Ctrl-A
- `command-prompt`: added support for ``Gui::getSelectedPlant()``
- `gui/advfort`: bound to Ctrl-T
- `gui/room-list`: added support for ``Gui::getSelectedBuilding()``
- `gui/unit-info-viewer`: bound to Alt-I
- `modtools/create-unit`: made functions available to other scripts
- `search`:
- added support for stone restrictions screen (under ``z``: Status)
- added support for kitchen preferences (also under ``z``)
## Internals
- Fixed compiler warnings on all supported build configurations
- Windows build scripts now work with non-C system drives
## API
- New functions (all available to Lua as well):
- ``Buildings::getRoomDescription()``
- ``Items::checkMandates()``
- ``Items::canTrade()``
- ``Items::canTradeWithContents()``
- ``Items::isRouteVehicle()``
- ``Items::isSquadEquipment()``
- ``Kitchen::addExclusion()``
- ``Kitchen::findExclusion()``
- ``Kitchen::removeExclusion()``
- syndrome-util: added ``eraseSyndromeData()``
## Structures
- ``dfhack_room_quality_level``: new enum
- ``glowing_barrier``: identified ``triggered``, added comments
- ``item_flags2``: renamed ``has_written_content`` to ``unk_book``
- ``kitchen_exc_type``: new enum (for ``ui.kitchen``)
- ``mandate.mode``: now an enum
- ``unit_personality.emotions.flags.memory``: identified
- ``viewscreen_kitchenprefst.forbidden``, ``possible``: now a bitfield, ``kitchen_pref_flag``
- ``world_data.feature_map``: added extensive documentation (in XML)
================================================================================
# 0.44.10-beta1
## New Scripts
- `devel/find-primitive`: finds a primitive variable in memory
## Fixes
- Units::getAnyUnit(): fixed a couple problematic conditions and potential segfaults if global addresses are missing
- `stockpiles`: stopped sidebar option from overlapping with `autodump`
-@ `autodump`, `automelt`, `autotrade`, `stocks`, `stockpiles`: fixed conflict with building renaming
- `tweak` block-labors: fixed two causes of crashes related in the v-p-l menu
- `full-heal`:
- units no longer have a tendency to melt after being healed
- healed units are no longer treated as patients by hospital staff
- healed units no longer attempt to clean themselves unsuccessfully
- wounded fliers now regain the ability to fly upon being healing
- now heals suffocation, numbness, infection, spilled guts and gelding
- `modtools/create-unit`:
- creatures of the appropriate age are now spawned as babies or children where applicable
- fix: civ_id is now properly assigned to historical_figure, resolving several hostility issues (spawned pets are no longer attacked by fortress military!)
- fix: unnamed creatures are no longer spawned with a string of numbers as a first name
- `exterminate`: fixed documentation of ``this`` option
## Misc Improvements
- `blueprint`: added a basic Lua API
- `devel/export-dt-ini`: added tool offsets for DT 40
- `devel/save-version`: added current DF version to output
- `install-info`: added information on tweaks
## Internals
- Added ``Gui::inRenameBuilding()``
- 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
================================================================================
# 0.44.10-alpha1
## New Scripts
- `caravan`: adjusts properties of caravans
- `gui/autogems`: a configuration UI for the `autogems` plugin
## 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
- `search`: fixed 4/6 keys in unit screen search
- `view-item-info`: fixed an error with some armor
## Misc Improvements
- `autogems`: can now blacklist arbitrary gem types (see `gui/autogems`)
- `exterminate`: added more words for current unit, removed warning
- `fpause`: now pauses worldgen as well
## Internals
- Added some build scripts for Sublime Text
- Changed submodule URLs to relative URLs so that they can be cloned consistently over different protocols (e.g. SSH)
================================================================================
# 0.44.09-r1
## Internals
- OS X: Can now build with GCC 7 (or older)
## Fixes
- `modtools/item-trigger`: fixed token format in help text
## Misc Improvements
- Reorganized changelogs and improved changelog editing process
- `modtools/item-trigger`: added support for multiple type/material/contaminant conditions
## Structures
-@ ``renderer``: fixed vtable addresses on 64-bit OS X
- ``building_type``: added human-readable ``name`` attribute
- ``furnace_type``: added human-readable ``name`` attribute
- ``workshop_type``: added human-readable ``name`` attribute
- ``army``: added vector new in 0.44.07
- ``site_reputation_report``: named ``reports`` vector
================================================================================
# 0.44.09-alpha1
## Fixes
- `digtype`: stopped designating non-vein tiles (open space, trees, etc.)
- `labormanager`: fixed crash due to dig jobs targeting some unrevealed map blocks
================================================================================
# 0.44.08-alpha1
## Fixes
- `fix/dead-units`: fixed a bug that could remove some arriving (not dead) units
================================================================================
# 0.44.07-beta1
## Structures
-@ Added symbols for Toady's `0.44.07 Linux test build <http://www.bay12forums.com/smf/index.php?topic=169839.msg7720111#msg7720111>`_ to fix :bug:`10615`
-@ ``world_site``: fixed alignment
## Misc improvements
- `modtools/item-trigger`: added the ability to specify inventory mode(s) to trigger on
================================================================================
# 0.44.07-alpha1
## Fixes
- Support for building on Ubuntu 18.04
- Fixed some CMake warnings (CMP0022)
- `embark-assistant`: fixed detection of reanimating biomes
## Misc Improvements
- `embark-assistant`:
- Added search for adamantine
- Now supports saving/loading profiles
- `fillneeds`: added ``-all`` option to apply to all units
- `remotefortressreader`: added flows, instruments, tool names, campfires, ocean waves, spiderwebs
## Structures
- Several new names in instrument raw structures
- ``identity``: identified ``profession``, ``civ``
- ``manager_order_template``: fixed last field type
- ``viewscreen_createquotast``: fixed layout
- ``world.language``: moved ``colors``, ``shapes``, ``patterns`` to ``world.descriptors``
- ``world.reactions``, ``world.reaction_categories``:\ moved to new compound, ``world.reactions``. Requires renaming:
- ``world.reactions`` to ``world.reactions.reactions``
- ``world.reaction_categories`` to ``world.reactions.reaction_categories``
================================================================================
# 0.44.05-r2
## Fixes
- `devel/export-dt-ini`: fix language_name offsets for DT 39.2+
- `devel/inject-raws`: fixed gloves and shoes (old typo causing errors)
- `remotefortressreader`: fixed an issue with not all engravings being included
- `view-item-info`: fixed an error with some shields
## Misc Improvements
- `adv-rumors`: added more keywords, including names
- `autochop`: can now exclude trees that produce fruit, food, or cookable items
- `remotefortressreader`: added plant type support
## New Plugins
- `embark-assistant`: adds more information and features to embark screen
## New Scripts
- `adv-fix-sleepers`: fixes units in adventure mode who refuse to wake up (:bug:`6798`)
- `hermit`: blocks caravans, migrants, diplomats (for hermit challenge)
## New Features
- With ``PRINT_MODE:TEXT``, setting the ``DFHACK_HEADLESS`` environment variable will hide DF's display and allow the console to be used normally. (Note that this is intended for testing and is not very useful for actual gameplay.)
================================================================================
# 0.44.05-r1
## New Scripts
- `break-dance`: Breaks up a stuck dance activity
- `fillneeds`: Use with a unit selected to make them focused and unstressed
- `firestarter`: Lights things on fire: items, locations, entire inventories even!
- `flashstep`: Teleports adventurer to cursor
- `ghostly`: Turns an adventurer into a ghost or back
- `questport`: Sends your adventurer to the location of your quest log cursor
- `view-unit-reports`: opens the reports screen with combat reports for the selected unit
## Fixes
- `devel/inject-raws`: now recognizes spaces in reaction names
- `dig`: added support for designation priorities - fixes issues with designations from ``digv`` and related commands having extremely high priority
- `dwarfmonitor`:
- fixed display of creatures and poetic/music/dance forms on ``prefs`` screen
- added "view unit" option
- now exposes the selected unit to other tools
- `names`: fixed many errors
- `quicksave`: fixed an issue where the "Saving..." indicator often wouldn't appear
## Misc Improvements
- `gui/gm-unit`:
- added a profession editor
- misc. layout improvements
- `remotefortressreader`:
- support for moving adventurers
- support for vehicles, gem shapes, item volume, art images, item improvements
- `binpatch`: now reports errors for empty patch files
- `force`: now provides useful help
- `full-heal`:
- can now select corpses to resurrect
- now resets body part temperatures upon resurrection to prevent creatures from freezing/melting again
- now resets units' vanish countdown to reverse effects of `exterminate`
- `launch`: can now ride creatures
- `names`: can now edit names of units
## Removed
- `tweak`: ``kitchen-keys``: :bug:`614` fixed in DF 0.44.04
## Internals
- ``Gui::getAnyUnit()`` supports many more screens/menus
## Structures
- New globals: ``soul_next_id``
================================================================================
# 0.44.05-alpha1
## Misc Improvements
- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards
## Structures
-@ ``incident``: re-aligned again to match disassembly
================================================================================
# 0.44.04-alpha1
## Fixes
- `devel/inject-raws`: now recognizes spaces in reaction names
- `exportlegends`: fixed an error that could occur when exporting empty lists
## Structures
- ``artifact_record``: fixed layout (changed in 0.44.04)
- ``incident``: fixed layout (changed in 0.44.01) - note that many fields have moved
================================================================================
# 0.44.03-beta1
## Fixes
- `autolabor`, `autohauler`, `labormanager`: added support for "put item on display" jobs and building/destroying display furniture
- `gui/gm-editor`: fixed an error when editing primitives in Lua tables
## Misc Improvements
- @ `devel/dump-offsets`: now ignores ``index`` globals
- `gui/pathable`: added tile types to sidebar
- `modtools/skill-change`:
- now updates skill levels appropriately
- only prints output if ``-loud`` is passed
## Structures
- New globals:
- ``version``
- ``min_load_version``
- ``movie_version``
- ``basic_seed``
- ``title``
- ``title_spaced``
- ``ui_building_resize_radius``
- Added ``twbt_render_map`` code offset on x64
- Fixed an issue preventing ``enabler`` from being allocated by DFHack
- Added ``job_type.PutItemOnDisplay``
- Found ``renderer`` vtable on osx64
- ``adventure_movement_optionst``, ``adventure_movement_hold_tilest``, ``adventure_movement_climbst``: named coordinate fields
- ``mission``: added type
- ``unit``: added 3 new vmethods: ``getCreatureTile``, ``getCorpseTile``, ``getGlowTile``
- ``viewscreen_assign_display_itemst``: fixed layout on x64 and identified many fields
- ``viewscreen_reportlistst``: fixed layout, added ``mission_id`` vector
- ``world.status``: named ``missions`` vector
================================================================================
# 0.44.03-alpha1
## Lua
- Improved ``json`` I/O error messages
- Stopped a crash when trying to create instances of classes whose vtable addresses are not available
================================================================================
# 0.44.02-beta1
## New Scripts
- `devel/check-other-ids`: Checks the validity of "other" vectors in the ``world`` global
- `gui/cp437-table`: An in-game CP437 table
## Fixes
- Fixed issues with the console output color affecting the prompt on Windows
- `createitem`: stopped items from teleporting away in some forts
- `gui/gm-unit`: can now edit mining skill
- `gui/quickcmd`: stopped error from adding too many commands
- `modtools/create-unit`: fixed error when domesticating units
## Misc Improvements
- The console now provides suggestions for built-in commands
- `devel/export-dt-ini`: avoid hardcoding flags
- `exportlegends`:
- reordered some tags to match DF's order
- added progress indicators for exporting long lists
- `gui/gm-editor`: added enum names to enum edit dialogs
- `gui/gm-unit`: made skill search case-insensitive
- `gui/rename`: added "clear" and "special characters" options
- `remotefortressreader`:
- includes item stack sizes
- some performance improvements
## Removed
- `warn-stuck-trees`: :bug:`9252` fixed in DF 0.44.01
## Lua
- Exposed ``get_vector()`` (from C++) for all types that support ``find()``, e.g. ``df.unit.get_vector() == df.global.world.units.all``
## Structures
- Located ``start_dwarf_count`` offset for all builds except 64-bit Linux; `startdwarf` should work now
- Added ``buildings_other_id.DISPLAY_CASE``
- Fixed ``viewscreen_titlest.start_savegames`` alignment
- Fixed ``unit`` alignment
- Identified ``historical_entity.unknown1b.deities`` (deity IDs)
================================================================================
# 0.44.02-alpha1
## New Scripts
- `devel/dump-offsets`: prints an XML version of the global table included in in DF
## Fixes
- Fixed a crash that could occur if a symbol table in symbols.xml had no content
## Lua
- Added a new ``dfhack.console`` API
- API can now wrap functions with 12 or 13 parameters
## Structures
- The ``ui_menu_width`` global is now a 2-byte array; the second item is the former ``ui_area_map_width`` global, which is now removed
- The former ``announcements`` global is now a field in ``d_init``
- ``world`` fields formerly beginning with ``job_`` are now fields of ``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list``

@ -0,0 +1,272 @@
import collections
import copy
import itertools
import os
import sys
CHANGELOG_SECTIONS = [
'New Plugins',
'New Scripts',
'New Tweaks',
'New Features',
'New Internal Commands',
'Fixes',
'Misc Improvements',
'Removed',
'API',
'Internals',
'Lua',
'Ruby',
'Structures',
]
REPLACEMENTS = {
'`search`': '`search-plugin`',
}
def to_title_case(word):
if word == word.upper():
# Preserve acronyms
return word
return word[0].upper() + word[1:].lower()
def find_all_indices(string, substr):
start = 0
while True:
i = string.find(substr, start)
if i == -1:
return
yield i
start = i + 1
def replace_text(string, replacements):
for old_text, new_text in replacements.items():
new_string = ''
new_string_end = 0 # number of characters from string in new_string
for i in find_all_indices(string, old_text):
if i > 0 and string[i - 1] == '!':
# exempt if preceded by '!'
new_string += string[new_string_end:i - 1]
new_string += old_text
else:
# copy until this occurrence
new_string += string[new_string_end:i]
new_string += new_text
new_string_end = i + len(old_text)
new_string += string[new_string_end:]
string = new_string
return string
class ChangelogEntry(object):
def __init__(self, text, section, stable_version, dev_version):
text = text.lstrip('- ')
# normalize section to title case
self.section = ' '.join(map(to_title_case, section.strip().split()))
self.stable_version = stable_version
self.dev_version = dev_version
self.dev_only = text.startswith('@')
text = text.lstrip('@ ')
self.children = []
split_index = text.find(': ')
if split_index != -1:
self.feature, description = text[:split_index], text[split_index+1:]
if description.strip():
self.children.insert(0, description.strip())
else:
self.feature = text
self.feature = self.feature.replace(':\\', ':').rstrip(':')
self.sort_key = self.feature.upper()
def __repr__(self):
return 'ChangelogEntry(%r, %r)' % (self.feature, self.children)
def parse_changelog():
cur_stable = None
cur_dev = None
cur_section = None
last_entry = None
entries = []
with open('docs/changelog.txt') as f:
multiline = ''
for line_id, line in enumerate(f.readlines()):
line_id += 1
if multiline:
multiline += line
elif '[[[' in line:
multiline = line.replace('[[[', '')
if ']]]' in multiline:
line = multiline.replace(']]]', '')
multiline = ''
elif multiline:
continue
if not line.strip() or line.startswith('==='):
continue
if line.startswith('##'):
cur_section = line.lstrip('#').strip()
elif line.startswith('#'):
cur_dev = line.lstrip('#').strip().lower()
if ('alpha' not in cur_dev and 'beta' not in cur_dev and
'rc' not in cur_dev):
cur_stable = cur_dev
elif line.startswith('-'):
if not cur_stable or not cur_dev or not cur_section:
raise ValueError(
'changelog.txt:%i: Entry without section' % line_id)
last_entry = ChangelogEntry(line.strip(), cur_section,
cur_stable, cur_dev)
entries.append(last_entry)
elif line.lstrip().startswith('-'):
if not cur_stable or not cur_dev:
raise ValueError(
'changelog.txt:%i: Sub-entry without section' % line_id)
if not last_entry:
raise ValueError(
'changelog.txt:%i: Sub-entry without parent' % line_id)
last_entry.children.append(line.strip('- \n'))
else:
raise ValueError('Invalid line: ' + line)
return entries
def consolidate_changelog(all_entries):
for sections in all_entries.values():
for section, entries in sections.items():
# sort() is stable, so reverse entries so that older entries for the
# same feature are on top
entries.reverse()
entries.sort(key=lambda entry: entry.sort_key)
new_entries = []
for feature, group in itertools.groupby(entries,
lambda e: e.feature):
old_entries = list(group)
children = list(itertools.chain(*[entry.children
for entry in old_entries]))
new_entry = copy.deepcopy(old_entries[0])
new_entry.children = children
new_entries.append(new_entry)
entries[:] = new_entries
def print_changelog(versions, all_entries, path, replace=True, prefix=''):
# all_entries: version -> section -> entry
with open(path, 'w') as f:
def write(line):
if replace:
line = replace_text(line, REPLACEMENTS)
f.write(prefix + line + '\n')
for version in versions:
sections = all_entries[version]
if not sections:
continue
version = 'DFHack ' + version
write(version)
write('=' * len(version))
write('')
for section in CHANGELOG_SECTIONS:
entries = sections[section]
if not entries:
continue
write(section)
write('-' * len(section))
for entry in entries:
if len(entry.children) == 1:
write('- ' + entry.feature + ': ' +
entry.children[0].strip('- '))
continue
elif entry.children:
write('- ' + entry.feature + ':')
write('')
for child in entry.children:
write(' - ' + child)
write('')
else:
write('- ' + entry.feature)
write('')
write('')
def generate_changelog(all=False):
entries = parse_changelog()
# scan for unrecognized sections
for entry in entries:
if entry.section not in CHANGELOG_SECTIONS:
raise RuntimeWarning('Unknown section: ' + entry.section)
# ordered versions
versions = ['future']
# map versions to stable versions
stable_version_map = {}
# version -> section -> entry
stable_entries = collections.defaultdict(lambda:
collections.defaultdict(list))
dev_entries = collections.defaultdict(lambda:
collections.defaultdict(list))
for entry in entries:
# build list of all versions
if entry.dev_version not in versions:
versions.append(entry.dev_version)
stable_version_map.setdefault(entry.dev_version, entry.stable_version)
if not entry.dev_only:
# add non-dev-only entries to both changelogs
stable_entries[entry.stable_version][entry.section].append(entry)
dev_entries[entry.dev_version][entry.section].append(entry)
consolidate_changelog(stable_entries)
print_changelog(versions, stable_entries, 'docs/_auto/news.rst')
print_changelog(versions, dev_entries, 'docs/_auto/news-dev.rst')
if all:
for version in versions:
if version not in stable_version_map:
print('warn: skipping ' + version)
continue
if stable_version_map[version] == version:
version_entries = {version: stable_entries[version]}
else:
version_entries = {version: dev_entries[version]}
print_changelog([version], version_entries,
'docs/_changelogs/%s-github.txt' % version,
replace=False)
print_changelog([version], version_entries,
'docs/_changelogs/%s-reddit.txt' % version,
replace=False,
prefix='> ')
return entries
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', '--all', action='store_true',
help='Print changelogs for all versions to docs/_changelogs')
parser.add_argument('-c', '--check', action='store_true',
help='Check that all entries are printed')
args = parser.parse_args()
os.chdir(os.path.abspath(os.path.dirname(__file__)))
os.chdir('..')
entries = generate_changelog(all=args.all)
if args.check:
with open('docs/_auto/news.rst') as f:
content_stable = f.read()
with open('docs/_auto/news-dev.rst') as f:
content_dev = f.read()
for entry in entries:
for description in entry.children:
if not entry.dev_only and description not in content_stable:
print('stable missing: ' + description)
if description not in content_dev:
print('dev missing: ' + description)

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@ -49,7 +49,7 @@ Other Contents
/docs/Authors /docs/Authors
/LICENSE /LICENSE
/NEWS /docs/NEWS
/docs/Scripts-removed /docs/Scripts-removed
For Developers For Developers

@ -0,0 +1 @@
git-describe.cmake

@ -1,10 +1,8 @@
PROJECT (dfapi) PROJECT (dfapi)
cmake_minimum_required(VERSION 2.8) cmake_minimum_required(VERSION 2.8.12)
# prevent CMake warnings about INTERFACE_LINK_LIBRARIES vs LINK_INTERFACE_LIBRARIES # prevent CMake warnings about INTERFACE_LINK_LIBRARIES vs LINK_INTERFACE_LIBRARIES
IF(CMAKE_VERSION VERSION_GREATER "2.8.12") CMAKE_POLICY(SET CMP0022 NEW)
CMAKE_POLICY(SET CMP0022 OLD)
ENDIF()
## build options ## build options
OPTION(BUILD_DEVEL "Install/package files required for development (For SDK)." OFF) OPTION(BUILD_DEVEL "Install/package files required for development (For SDK)." OFF)
@ -57,6 +55,7 @@ SET(MAIN_SOURCES
Core.cpp Core.cpp
ColorText.cpp ColorText.cpp
DataDefs.cpp DataDefs.cpp
Error.cpp
VTableInterpose.cpp VTableInterpose.cpp
LuaWrapper.cpp LuaWrapper.cpp
LuaTypes.cpp LuaTypes.cpp
@ -121,7 +120,7 @@ include/modules/Gui.h
include/modules/GuiHooks.h include/modules/GuiHooks.h
include/modules/Items.h include/modules/Items.h
include/modules/Job.h include/modules/Job.h
include/modules/kitchen.h include/modules/Kitchen.h
include/modules/MapCache.h include/modules/MapCache.h
include/modules/Maps.h include/modules/Maps.h
include/modules/Materials.h include/modules/Materials.h
@ -132,7 +131,6 @@ include/modules/Renderer.h
include/modules/Screen.h include/modules/Screen.h
include/modules/Translation.h include/modules/Translation.h
include/modules/Units.h include/modules/Units.h
include/modules/Vermin.h
include/modules/World.h include/modules/World.h
) )
@ -148,7 +146,7 @@ modules/Graphic.cpp
modules/Gui.cpp modules/Gui.cpp
modules/Items.cpp modules/Items.cpp
modules/Job.cpp modules/Job.cpp
modules/kitchen.cpp modules/Kitchen.cpp
modules/MapCache.cpp modules/MapCache.cpp
modules/Maps.cpp modules/Maps.cpp
modules/Materials.cpp modules/Materials.cpp
@ -159,7 +157,6 @@ modules/Renderer.cpp
modules/Screen.cpp modules/Screen.cpp
modules/Translation.cpp modules/Translation.cpp
modules/Units.cpp modules/Units.cpp
modules/Vermin.cpp
modules/Windows.cpp modules/Windows.cpp
modules/World.cpp modules/World.cpp
) )
@ -245,6 +242,10 @@ ADD_CUSTOM_COMMAND(
DEPENDS protoc-bin ${PROJECT_PROTOS} DEPENDS protoc-bin ${PROJECT_PROTOS}
) )
IF(UNIX)
SET_SOURCE_FILES_PROPERTIES(${PROJECT_PROTO_SRCS} PROPERTIES COMPILE_FLAGS "-Wno-misleading-indentation")
ENDIF()
ADD_CUSTOM_TARGET(generate_proto_core DEPENDS ${PROJECT_PROTO_TMP_FILES}) ADD_CUSTOM_TARGET(generate_proto_core DEPENDS ${PROJECT_PROTO_TMP_FILES})
# Merge headers into sources # Merge headers into sources
@ -255,25 +256,38 @@ LIST(APPEND PROJECT_SOURCES ${PROJECT_HEADERS})
LIST(APPEND PROJECT_SOURCES ${GENERATED_HDRS}) LIST(APPEND PROJECT_SOURCES ${GENERATED_HDRS})
FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR}/xml/*.xslt) FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR}/xml/*.xslt)
FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/*.xml) FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml)
set(CODEGEN_OUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml)
LIST(APPEND CODEGEN_OUT ${GENERATED_HDRS})
ADD_CUSTOM_COMMAND( ADD_CUSTOM_COMMAND(
OUTPUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml OUTPUT ${CODEGEN_OUT}
COMMAND ${PERL_EXECUTABLE} xml/codegen.pl xml include/df COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xml/codegen.pl
WORKING_DIRECTORY ${dfapi_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/xml
${CMAKE_CURRENT_SOURCE_DIR}/include/df
MAIN_DEPENDENCY ${dfapi_SOURCE_DIR}/xml/codegen.pl MAIN_DEPENDENCY ${dfapi_SOURCE_DIR}/xml/codegen.pl
COMMENT "Generating codegen.out.xml and df/headers"
DEPENDS ${GENERATE_INPUT_XMLS} ${GENERATE_INPUT_SCRIPTS} DEPENDS ${GENERATE_INPUT_XMLS} ${GENERATE_INPUT_SCRIPTS}
) )
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(UNIX) IF(REMOVE_SYMBOLS_FROM_DF_STUBS)
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()
ELSE()
IF(WIN32)
SET_SOURCE_FILES_PROPERTIES(DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp ${STATIC_FIELDS_FILES}
PROPERTIES COMPILE_FLAGS "/Od /bigobj")
ENDIF()
ENDIF() ENDIF()
# Compilation # Compilation
@ -295,11 +309,19 @@ ELSE(WIN32)
SET(PROJECT_LIBS psapi dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread) SET(PROJECT_LIBS psapi dfhack-md5 ${DFHACK_TINYXML} dfhack-tinythread)
ENDIF() ENDIF()
ADD_LIBRARY(dfhack-version STATIC DFHackVersion.cpp) set(VERSION_SRCS DFHackVersion.cpp)
set(VERSION_HDRS include/git-describe.h)
SET_SOURCE_FILES_PROPERTIES(${VERSION_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
LIST(APPEND VERSION_SRCS ${VERSION_HDRS})
ADD_LIBRARY(dfhack-version STATIC ${VERSION_SRCS})
SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS
DFHACK_VERSION="${DFHACK_VERSION}" DFHACK_VERSION="${DFHACK_VERSION}"
DF_VERSION="${DF_VERSION}" DF_VERSION="${DF_VERSION}"
DFHACK_RELEASE="${DFHACK_RELEASE}" DFHACK_RELEASE="${DFHACK_RELEASE}"
DFHACK_ABI_VERSION=${DFHACK_ABI_VERSION}
) )
IF(DFHACK_PRERELEASE) IF(DFHACK_PRERELEASE)
SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS SET_PROPERTY(TARGET dfhack-version APPEND PROPERTY COMPILE_DEFINITIONS
@ -307,19 +329,26 @@ IF(DFHACK_PRERELEASE)
) )
ENDIF() ENDIF()
ADD_CUSTOM_TARGET(git-describe configure_file(git-describe.cmake.in ${CMAKE_CURRENT_SOURCE_DIR}/git-describe.cmake @ONLY)
if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index AND EXISTS ${CMAKE_SOURCE_DIR}/.git/modules/library/xml/index)
ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/include/git-describe.h
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
-D dfhack_SOURCE_DIR:STRING=${dfhack_SOURCE_DIR} -D dfhack_SOURCE_DIR:STRING=${dfhack_SOURCE_DIR}
-D GIT_EXECUTABLE:STRING=${GIT_EXECUTABLE} -D GIT_EXECUTABLE:STRING=${GIT_EXECUTABLE}
-P ${CMAKE_CURRENT_SOURCE_DIR}/git-describe.cmake -P ${CMAKE_CURRENT_SOURCE_DIR}/git-describe.cmake
COMMENT "Obtaining git commit information" COMMENT "Obtaining git commit information"
) DEPENDS ${CMAKE_SOURCE_DIR}/.git/index
ADD_DEPENDENCIES(dfhack-version git-describe) ${CMAKE_SOURCE_DIR}/.git/modules/library/xml/index
${CMAKE_CURRENT_SOURCE_DIR}/git-describe.cmake
include/git-describe.h.in
)
endif ()
ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES}) ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES})
ADD_DEPENDENCIES(dfhack generate_headers generate_proto_core) ADD_DEPENDENCIES(dfhack generate_proto_core)
ADD_DEPENDENCIES(dfhack generate_headers)
ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${PROJECT_PROTO_SRCS}) ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS})
ADD_DEPENDENCIES(dfhack-client dfhack) ADD_DEPENDENCIES(dfhack-client dfhack)
ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp)
@ -363,10 +392,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 LINK_INTERFACE_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)
@ -379,8 +408,6 @@ IF(UNIX)
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/osx${DFHACK_BUILD_ARCH}/libstdc++.6.dylib
DESTINATION ./hack/)
else() else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD # On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack

@ -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,25 @@ 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++; const int text_over = plen + cooked_cursor + 1 - cols;
len--; begin = text_over;
cooked_cursor--; len -= text_over;
} cooked_cursor -= text_over;
while (plen+len > cols)
{
len--;
} }
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 +519,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 +530,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 +563,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 +578,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,11 +594,11 @@ 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')
{ {
left_arrow: /* left arrow */
if (raw_cursor > 0) if (raw_cursor > 0)
{ {
raw_cursor--; raw_cursor--;
@ -541,7 +607,6 @@ namespace DFHack
} }
else if ( seq[1] == 'C') else if ( seq[1] == 'C')
{ {
right_arrow:
/* right arrow */ /* right arrow */
if (size_t(raw_cursor) != raw_buffer.size()) if (size_t(raw_cursor) != raw_buffer.size())
{ {
@ -556,7 +621,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)
@ -569,7 +634,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();
} }
@ -594,7 +659,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 == '~' )
@ -610,7 +675,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 == ';')
{ {
@ -636,7 +701,7 @@ namespace DFHack
prompt_refresh(); prompt_refresh();
break; break;
case 11: // Ctrl+k, delete from current to end of line. case 11: // Ctrl+k, delete from current to end of line.
if (raw_cursor < raw_buffer.size()) if (size_t(raw_cursor) < raw_buffer.size())
yank_buffer = raw_buffer.substr(raw_cursor); yank_buffer = raw_buffer.substr(raw_cursor);
raw_buffer.erase(raw_cursor); raw_buffer.erase(raw_cursor);
prompt_refresh(); prompt_refresh();
@ -652,7 +717,7 @@ namespace DFHack
case 20: // Ctrl+t, transpose current and previous characters case 20: // Ctrl+t, transpose current and previous characters
if (raw_buffer.size() >= 2 && raw_cursor > 0) if (raw_buffer.size() >= 2 && raw_cursor > 0)
{ {
if (raw_cursor == raw_buffer.size()) if (size_t(raw_cursor) == raw_buffer.size())
raw_cursor--; raw_cursor--;
std::swap(raw_buffer[raw_cursor - 1], raw_buffer[raw_cursor]); std::swap(raw_buffer[raw_cursor - 1], raw_buffer[raw_cursor]);
raw_cursor++; raw_cursor++;
@ -673,15 +738,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
{ {
@ -690,7 +771,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();
} }
@ -713,8 +794,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];
@ -731,8 +812,7 @@ Console::Console()
} }
Console::~Console() Console::~Console()
{ {
if(inited) assert(!inited);
shutdown();
if(wlock) if(wlock)
delete wlock; delete wlock;
if(d) if(d)
@ -769,11 +849,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;
} }
@ -815,7 +890,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;
@ -824,7 +899,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;
@ -854,9 +929,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();

@ -54,6 +54,7 @@ using namespace std;
#include "modules/Graphic.h" #include "modules/Graphic.h"
#include "modules/Windows.h" #include "modules/Windows.h"
#include "RemoteServer.h" #include "RemoteServer.h"
#include "RemoteTools.h"
#include "LuaTools.h" #include "LuaTools.h"
#include "DFHackVersion.h" #include "DFHackVersion.h"
@ -69,6 +70,7 @@ using namespace DFHack;
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_game_cleanerst.h" #include "df/viewscreen_game_cleanerst.h"
#include "df/viewscreen_loadgamest.h" #include "df/viewscreen_loadgamest.h"
#include "df/viewscreen_new_regionst.h"
#include "df/viewscreen_savegamest.h" #include "df/viewscreen_savegamest.h"
#include <df/graphic.h> #include <df/graphic.h>
@ -76,12 +78,17 @@ 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"
using namespace tthread; #ifdef LINUX_BUILD
#include <dlfcn.h>
#endif
using namespace df::enums; using namespace df::enums;
using df::global::init; using df::global::init;
using df::global::world; using df::global::world;
@ -91,48 +98,42 @@ 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 {
{ class MainThread {
Cond() public:
{ //! MainThread::suspend keeps the main DF thread suspended from Core::Init to
predicate = false; //! thread exit.
wakeup = new tthread::condition_variable(); static CoreSuspenderBase& suspend() {
} static thread_local CoreSuspenderBase lock(std::defer_lock);
~Cond() return lock;
{
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
@ -222,9 +223,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());
@ -261,6 +263,8 @@ static string dfhack_version_desc()
else else
s << "(development build " << Version::git_description() << ")"; s << "(development build " << Version::git_description() << ")";
s << " on " << (sizeof(void*) == 8 ? "x86_64" : "x86"); s << " on " << (sizeof(void*) == 8 ? "x86_64" : "x86");
if (strlen(Version::dfhack_build_id()))
s << " [build ID: " << Version::dfhack_build_id() << "]";
return s.str(); return s.str();
} }
@ -505,7 +509,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;
@ -517,7 +521,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++)
{ {
@ -536,7 +540,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)
@ -655,6 +659,9 @@ string getBuiltinCommand(std::string cmd)
else if (cmd == "clear") else if (cmd == "clear")
builtin = "cls"; builtin = "cls";
else if (cmd == "devel/dump-rpc")
builtin = "devel/dump-rpc";
return builtin; return builtin;
} }
@ -1145,6 +1152,10 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
else if (builtin == "fpause") else if (builtin == "fpause")
{ {
World::SetPauseState(true); World::SetPauseState(true);
if (auto scr = Gui::getViewscreenByType<df::viewscreen_new_regionst>())
{
scr->worldgen_paused = true;
}
con.print("The game was forced to pause!\n"); con.print("The game was forced to pause!\n");
} }
else if (builtin == "cls") else if (builtin == "cls")
@ -1159,7 +1170,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
else if (builtin == "die") else if (builtin == "die")
{ {
_exit(666); std::_Exit(666);
} }
else if (builtin == "kill-lua") else if (builtin == "kill-lua")
{ {
@ -1297,6 +1308,34 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
} }
else if (builtin == "devel/dump-rpc")
{
if (parts.size() == 1)
{
std::ofstream file(parts[0]);
CoreService core;
core.dumpMethods(file);
for (auto & it : *plug_mgr)
{
Plugin * plug = it.second;
if (!plug)
continue;
std::unique_ptr<RPCService> svc(plug->rpc_connect(con));
if (!svc)
continue;
file << "// Plugin: " << plug->getName() << endl;
svc->dumpMethods(file);
}
}
else
{
con << "Usage: devel/dump-rpc \"filename\"" << endl;
return CR_WRONG_USAGE;
}
}
else if (RunAlias(con, first, parts, res)) else if (RunAlias(con, first, parts, res))
{ {
return res; return res;
@ -1330,7 +1369,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");
@ -1437,16 +1476,22 @@ void fIOthread(void * iodata)
dfhack_version_desc().c_str()); dfhack_version_desc().c_str());
int clueless_counter = 0; int clueless_counter = 0;
if (getenv("DFHACK_DISABLE_CONSOLE"))
return;
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;
@ -1471,10 +1516,23 @@ void fIOthread(void * iodata)
} }
} }
Core::Core() Core::~Core()
{ {
d = new Private(); // we leak the memory in case ~Core is called after _exit
}
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;
@ -1485,11 +1543,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;
@ -1499,7 +1553,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)
@ -1534,8 +1587,6 @@ std::string Core::getHackPath()
#endif #endif
} }
void init_screen_module(Core *);
bool Core::Init() bool Core::Init()
{ {
if(started) if(started)
@ -1543,6 +1594,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.
@ -1619,14 +1674,42 @@ bool Core::Init()
} }
cerr << "Version: " << vinfo->getVersion() << endl; cerr << "Version: " << vinfo->getVersion() << endl;
#if defined(_WIN32)
const OSType expected = OS_WINDOWS;
#elif defined(_DARWIN)
const OSType expected = OS_APPLE;
#else
const OSType expected = OS_LINUX;
#endif
if (expected != vinfo->getOS()) {
cerr << "OS mismatch; resetting to " << int(expected) << endl;
vinfo->setOS(expected);
}
// 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.
bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT)); bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT));
if (is_text_mode || getenv("DFHACK_DISABLE_CONSOLE")) bool is_headless = bool(getenv("DFHACK_HEADLESS"));
if (is_headless)
{
#ifdef LINUX_BUILD
auto endwin = (int(*)(void))dlsym(RTLD_DEFAULT, "endwin");
if (endwin)
{
endwin();
}
else
{
cerr << "endwin(): bind failed" << endl;
}
#else
cerr << "Headless mode not supported on Windows" << endl;
#endif
}
if (is_text_mode && !is_headless)
{ {
con.init(true); con.init(true);
cerr << "Console is not available. Use dfhack-run to send commands.\n"; cerr << "Console is not available. Use dfhack-run to send commands.\n";
@ -1650,7 +1733,6 @@ bool Core::Init()
*/ */
// initialize data defs // initialize data defs
virtual_identity::Init(this); virtual_identity::Init(this);
init_screen_module(this);
// copy over default config files if necessary // copy over default config files if necessary
std::vector<std::string> config_files; std::vector<std::string> config_files;
@ -1694,7 +1776,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);
@ -1703,27 +1784,21 @@ bool Core::Init()
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;
HotkeyMutex = new mutex(); if (!is_text_mode || is_headless)
HotkeyCond = new condition_variable();
if (!is_text_mode)
{ {
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;
@ -1737,7 +1812,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())
{ {
@ -1802,28 +1877,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;
} }
@ -1849,82 +1921,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()
@ -1935,40 +1954,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");
@ -2071,8 +2056,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;
@ -2088,27 +2071,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;
}; };
@ -2322,12 +2287,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)
{ {
@ -2341,7 +2325,8 @@ int Core::Shutdown ( void )
} }
allModules.clear(); allModules.clear();
memset(&(s_mods), 0, sizeof(s_mods)); memset(&(s_mods), 0, sizeof(s_mods));
con.shutdown(); delete d;
d = nullptr;
return -1; return -1;
} }
@ -2492,7 +2477,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];
@ -2591,7 +2576,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) {
@ -2630,7 +2615,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];
@ -2654,7 +2639,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) {
@ -2674,7 +2659,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;
@ -2685,7 +2670,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);
@ -2696,14 +2681,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;
@ -2718,13 +2703,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

@ -3,6 +3,10 @@
#include "git-describe.h" #include "git-describe.h"
namespace DFHack { namespace DFHack {
namespace Version { namespace Version {
int dfhack_abi_version()
{
return DFHACK_ABI_VERSION;
}
const char *dfhack_version() const char *dfhack_version()
{ {
return DFHACK_VERSION; return DFHACK_VERSION;
@ -15,6 +19,10 @@ namespace DFHack {
{ {
return DFHACK_RELEASE; return DFHACK_RELEASE;
} }
const char *dfhack_build_id()
{
return DFHACK_BUILD_ID;
}
const char *git_description() const char *git_description()
{ {
return DFHACK_GIT_DESCRIPTION; return DFHACK_GIT_DESCRIPTION;

@ -143,11 +143,30 @@ enum_identity::enum_identity(size_t size,
type_identity *base_type, type_identity *base_type,
int64_t first_item_value, int64_t last_item_value, int64_t first_item_value, int64_t last_item_value,
const char *const *keys, const char *const *keys,
const ComplexData *complex,
const void *attrs, struct_identity *attr_type) const void *attrs, struct_identity *attr_type)
: compound_identity(size, NULL, scope_parent, dfhack_name), : compound_identity(size, NULL, scope_parent, dfhack_name),
keys(keys), first_item_value(first_item_value), last_item_value(last_item_value), keys(keys), complex(complex),
first_item_value(first_item_value), last_item_value(last_item_value),
base_type(base_type), attrs(attrs), attr_type(attr_type) base_type(base_type), attrs(attrs), attr_type(attr_type)
{ {
if (complex) {
count = complex->size();
last_item_value = complex->index_value_map.back();
}
else {
count = int(last_item_value-first_item_value+1);
}
}
enum_identity::ComplexData::ComplexData(std::initializer_list<int64_t> values)
{
size_t i = 0;
for (int64_t value : values) {
value_index_map[value] = i;
index_value_map.push_back(value);
i++;
}
} }
struct_identity::struct_identity(size_t size, TAllocateFn alloc, struct_identity::struct_identity(size_t size, TAllocateFn alloc,

@ -0,0 +1,43 @@
#include "Error.h"
#include "MiscUtils.h"
using namespace DFHack::Error;
inline std::string safe_str(const char *s)
{
return s ? s : "(NULL)";
}
NullPointer::NullPointer(const char *varname, const char *func)
:All("In " + safe_str(func) + ": NULL pointer: " + safe_str(varname)),
varname(varname)
{}
InvalidArgument::InvalidArgument(const char *expr, const char *func)
:All("In " + safe_str(func) + ": Invalid argument; expected: " + safe_str(expr)),
expr(expr)
{}
VTableMissing::VTableMissing(const char *name)
:All("Missing vtable address: " + safe_str(name)),
name(name)
{}
SymbolsXmlParse::SymbolsXmlParse(const char* desc, int id, int row, int col)
:AllSymbols(stl_sprintf("error %d: %s, at row %d col %d", id, desc, row, col)),
desc(safe_str(desc)), id(id), row(row), col(col)
{}
SymbolsXmlBadAttribute::SymbolsXmlBadAttribute(const char *attr)
:AllSymbols("attribute is either missing or invalid: " + safe_str(attr)),
attr(safe_str(attr))
{}
SymbolsXmlNoRoot::SymbolsXmlNoRoot()
:AllSymbols("no root element")
{}
SymbolsXmlUnderspecifiedEntry::SymbolsXmlUnderspecifiedEntry(const char *where)
:AllSymbols("Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: " + safe_str(where)),
where(safe_str(where))
{}

@ -88,6 +88,10 @@ DFhackCExport int SDL_PollEvent(SDL::Event* event)
struct WINDOW; struct WINDOW;
DFhackCExport int wgetch(WINDOW *win) DFhackCExport int wgetch(WINDOW *win)
{ {
if (getenv("DFHACK_HEADLESS"))
{
return 0;
}
static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch"); static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch");
if(!_wgetch) if(!_wgetch)
{ {

@ -26,6 +26,7 @@ distribution.
#include <windows.h> #include <windows.h>
#include <stdint.h> #include <stdint.h>
#include <mutex>
#include <vector> #include <vector>
#include <string> #include <string>
#include "Core.h" #include "Core.h"
@ -40,8 +41,8 @@ distribution.
// we don't know which of the SDL functions will be called first... so we // we don't know which of the SDL functions will be called first... so we
// just catch the first one and init all our function pointers at that time // just catch the first one and init all our function pointers at that time
bool FirstCall(void); static void InitSDLPointers(void);
bool inited = false; static std::once_flag inited;
/// wrappers for SDL 1.2 functions used in 40d16 /// wrappers for SDL 1.2 functions used in 40d16
/***** Condition variables /***** Condition variables
@ -463,7 +464,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;
@ -612,8 +613,7 @@ DFhackCExport int SDL_NumJoysticks(void)
static void (*_SDL_GL_SwapBuffers)(void) = 0; static void (*_SDL_GL_SwapBuffers)(void) = 0;
DFhackCExport void SDL_GL_SwapBuffers(void) DFhackCExport void SDL_GL_SwapBuffers(void)
{ {
if(!inited) InitSDLPointers();
FirstCall();
_SDL_GL_SwapBuffers(); _SDL_GL_SwapBuffers();
} }
@ -621,16 +621,14 @@ DFhackCExport void SDL_GL_SwapBuffers(void)
static int (*_SDL_Flip)(void * some_ptr) = 0; static int (*_SDL_Flip)(void * some_ptr) = 0;
DFhackCExport int SDL_Flip(void * some_ptr) DFhackCExport int SDL_Flip(void * some_ptr)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_Flip(some_ptr); return _SDL_Flip(some_ptr);
} }
static int (*_SDL_Init)(uint32_t flags) = 0; static int (*_SDL_Init)(uint32_t flags) = 0;
DFhackCExport int SDL_Init(uint32_t flags) DFhackCExport int SDL_Init(uint32_t flags)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_Init(flags); return _SDL_Init(flags);
} }
@ -640,16 +638,14 @@ MORE CRAP
static void * (*_SDL_CreateSemaphore)(uint32_t initial_value) = 0; static void * (*_SDL_CreateSemaphore)(uint32_t initial_value) = 0;
DFhackCExport void *SDL_CreateSemaphore(uint32_t initial_value) DFhackCExport void *SDL_CreateSemaphore(uint32_t initial_value)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_CreateSemaphore(initial_value); return _SDL_CreateSemaphore(initial_value);
} }
static vPtr (*_SDL_CreateThread)(int (*fn)(void *), void *data) = 0; static vPtr (*_SDL_CreateThread)(int (*fn)(void *), void *data) = 0;
DFhackCExport vPtr SDL_CreateThread(int (*fn)(void *), void *data) DFhackCExport vPtr SDL_CreateThread(int (*fn)(void *), void *data)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_CreateThread(fn,data); return _SDL_CreateThread(fn,data);
} }
@ -657,93 +653,81 @@ DFhackCExport vPtr SDL_CreateThread(int (*fn)(void *), void *data)
static void (*_SDL_Delay)(uint32_t ms) = 0; static void (*_SDL_Delay)(uint32_t ms) = 0;
DFhackCExport void SDL_Delay(uint32_t ms) DFhackCExport void SDL_Delay(uint32_t ms)
{ {
if(!inited) InitSDLPointers();
FirstCall();
_SDL_Delay(ms); _SDL_Delay(ms);
} }
static void (*_SDL_DestroySemaphore)(void *sem) = 0; static void (*_SDL_DestroySemaphore)(void *sem) = 0;
DFhackCExport void SDL_DestroySemaphore(void *sem) DFhackCExport void SDL_DestroySemaphore(void *sem)
{ {
if(!inited) InitSDLPointers();
FirstCall();
_SDL_DestroySemaphore(sem); _SDL_DestroySemaphore(sem);
} }
static uint8_t (*_SDL_GetAppState)(void) = 0; static uint8_t (*_SDL_GetAppState)(void) = 0;
DFhackCExport uint8_t SDL_GetAppState(void) DFhackCExport uint8_t SDL_GetAppState(void)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_GetAppState(); return _SDL_GetAppState();
} }
static uint8_t (*_SDL_GetMouseState)(int *, int *) = 0; static uint8_t (*_SDL_GetMouseState)(int *, int *) = 0;
DFhackCExport uint8_t SDL_GetMouseState(int *x, int *y) DFhackCExport uint8_t SDL_GetMouseState(int *x, int *y)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_GetMouseState(x,y); return _SDL_GetMouseState(x,y);
} }
static int (*_SDL_InitSubSystem)(uint32_t flags) = 0; static int (*_SDL_InitSubSystem)(uint32_t flags) = 0;
DFhackCExport int SDL_InitSubSystem(uint32_t flags) DFhackCExport int SDL_InitSubSystem(uint32_t flags)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_InitSubSystem(flags); return _SDL_InitSubSystem(flags);
} }
static int (*_SDL_SemPost)(void *sem) = 0; static int (*_SDL_SemPost)(void *sem) = 0;
DFhackCExport int SDL_SemPost(void *sem) DFhackCExport int SDL_SemPost(void *sem)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_SemPost(sem); return _SDL_SemPost(sem);
} }
static int (*_SDL_SemTryWait)(void *sem) = 0; static int (*_SDL_SemTryWait)(void *sem) = 0;
DFhackCExport int SDL_SemTryWait(void *sem) DFhackCExport int SDL_SemTryWait(void *sem)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_SemTryWait(sem); return _SDL_SemTryWait(sem);
} }
static int (*_SDL_SemWait)(void *sem) = 0; static int (*_SDL_SemWait)(void *sem) = 0;
DFhackCExport int SDL_SemWait(void *sem) DFhackCExport int SDL_SemWait(void *sem)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_SemWait(sem); return _SDL_SemWait(sem);
} }
static uint32_t (*_SDL_ThreadID)(void) = 0; static uint32_t (*_SDL_ThreadID)(void) = 0;
DFhackCExport uint32_t SDL_ThreadID(void) DFhackCExport uint32_t SDL_ThreadID(void)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_ThreadID(); return _SDL_ThreadID();
} }
static char* (*_SDL_getenv)(const char *name) = 0; static char* (*_SDL_getenv)(const char *name) = 0;
DFhackCExport char* SDL_getenv(const char *name) DFhackCExport char* SDL_getenv(const char *name)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_getenv(name); return _SDL_getenv(name);
} }
static size_t (*_SDL_strlcat)(char *dst, const char *src, size_t maxlen) = 0; static size_t (*_SDL_strlcat)(char *dst, const char *src, size_t maxlen) = 0;
DFhackCExport size_t SDL_strlcat(char *dst, const char *src, size_t maxlen) DFhackCExport size_t SDL_strlcat(char *dst, const char *src, size_t maxlen)
{ {
if(!inited) InitSDLPointers();
FirstCall();
return _SDL_strlcat(dst, src, maxlen); return _SDL_strlcat(dst, src, maxlen);
} }
// FIXME: this has to be thread-safe. void FirstCall()
bool FirstCall()
{ {
// reroute stdout and stderr // reroute stdout and stderr
freopen("stdout.log", "w", stdout); freopen("stdout.log", "w", stdout);
@ -753,7 +737,7 @@ bool FirstCall()
{ {
MessageBox(0,"Can't load SDLreal.dll\n","Error", MB_OK); MessageBox(0,"Can't load SDLreal.dll\n","Error", MB_OK);
fprintf(stderr, "Can't load SDLreal.dll\n"); fprintf(stderr, "Can't load SDLreal.dll\n");
return 0; return;
} }
fprintf(stderr, "FirstCall()\n"); fprintf(stderr, "FirstCall()\n");
// stuff for DF // stuff for DF
@ -836,6 +820,9 @@ bool FirstCall()
_SDL_EnableUNICODE(1); _SDL_EnableUNICODE(1);
fprintf(stderr,"Initized HOOKS!\n"); fprintf(stderr,"Initized HOOKS!\n");
inited = true; }
return 1;
void InitSDLPointers()
{
std::call_once(inited, [](){ FirstCall(); });
} }

@ -43,22 +43,23 @@ distribution.
#include "tinythread.h" #include "tinythread.h"
#include "md5wrapper.h" #include "md5wrapper.h"
#include "modules/World.h" #include "modules/Buildings.h"
#include "modules/Burrows.h"
#include "modules/Constructions.h"
#include "modules/Designations.h"
#include "modules/Filesystem.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Job.h"
#include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Materials.h" #include "modules/Job.h"
#include "modules/Maps.h" #include "modules/Kitchen.h"
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "modules/Burrows.h" #include "modules/Maps.h"
#include "modules/Buildings.h" #include "modules/Materials.h"
#include "modules/Constructions.h"
#include "modules/Random.h" #include "modules/Random.h"
#include "modules/Filesystem.h" #include "modules/Screen.h"
#include "modules/Designations.h" #include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "LuaWrapper.h" #include "LuaWrapper.h"
#include "LuaTools.h" #include "LuaTools.h"
@ -1415,7 +1416,7 @@ static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); }
static std::string df2utf(std::string s) { return DF2UTF(s); } static std::string df2utf(std::string s) { return DF2UTF(s); }
static std::string utf2df(std::string s) { return UTF2DF(s); } static std::string utf2df(std::string s) { return UTF2DF(s); }
static std::string df2console(std::string s) { return DF2CONSOLE(s); } static std::string df2console(color_ostream &out, std::string s) { return DF2CONSOLE(out, s); }
#define WRAP_VERSION_FUNC(name, function) WRAPN(name, DFHack::Version::function) #define WRAP_VERSION_FUNC(name, function) WRAPN(name, DFHack::Version::function)
@ -1435,6 +1436,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
WRAP(df2console), WRAP(df2console),
WRAP_VERSION_FUNC(getDFHackVersion, dfhack_version), WRAP_VERSION_FUNC(getDFHackVersion, dfhack_version),
WRAP_VERSION_FUNC(getDFHackRelease, dfhack_release), WRAP_VERSION_FUNC(getDFHackRelease, dfhack_release),
WRAP_VERSION_FUNC(getDFHackBuildID, dfhack_build_id),
WRAP_VERSION_FUNC(getCompiledDFVersion, df_version), WRAP_VERSION_FUNC(getCompiledDFVersion, df_version),
WRAP_VERSION_FUNC(getGitDescription, git_description), WRAP_VERSION_FUNC(getGitDescription, git_description),
WRAP_VERSION_FUNC(getGitCommit, git_commit), WRAP_VERSION_FUNC(getGitCommit, git_commit),
@ -1493,6 +1495,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, resetDwarfmodeView), WRAPM(Gui, resetDwarfmodeView),
WRAPM(Gui, revealInDwarfmodeMap), WRAPM(Gui, revealInDwarfmodeMap),
WRAPM(Gui, refreshSidebar), WRAPM(Gui, refreshSidebar),
WRAPM(Gui, inRenameBuilding),
WRAPM(Gui, getDepthAt), WRAPM(Gui, getDepthAt),
{ NULL, NULL } { NULL, NULL }
}; };
@ -1504,8 +1507,18 @@ static const luaL_Reg dfhack_gui_funcs[] = {
/***** Job module *****/ /***** Job module *****/
static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } static bool jobEqual(const df::job *job1, const df::job *job2)
static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } {
CHECK_NULL_POINTER(job1);
CHECK_NULL_POINTER(job2);
return *job1 == *job2;
}
static bool jobItemEqual(const df::job_item *job1, const df::job_item *job2)
{
CHECK_NULL_POINTER(job1);
CHECK_NULL_POINTER(job2);
return *job1 == *job2;
}
static const LuaWrapper::FunctionReg dfhack_job_module[] = { static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,cloneJobStruct), WRAPM(Job,cloneJobStruct),
@ -1614,6 +1627,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),
@ -1621,10 +1635,15 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, isGay), WRAPM(Units, isGay),
WRAPM(Units, isNaked), WRAPM(Units, isNaked),
WRAPM(Units, isUndead), WRAPM(Units, isUndead),
WRAPM(Units, isGhost),
WRAPM(Units, isActive),
WRAPM(Units, isKilled),
WRAPM(Units, isGelded), WRAPM(Units, isGelded),
WRAPM(Units, isDomesticated), WRAPM(Units, isDomesticated),
WRAPM(Units, getMainSocialActivity), WRAPM(Units, getMainSocialActivity),
WRAPM(Units, getMainSocialEvent), WRAPM(Units, getMainSocialEvent),
WRAPM(Units, getStressCategory),
WRAPM(Units, getStressCategoryRaw),
{ NULL, NULL } { NULL, NULL }
}; };
@ -1675,10 +1694,19 @@ static int units_getUnitsInBox(lua_State *state)
return 2; return 2;
} }
static int units_getStressCutoffs(lua_State *L)
{
lua_newtable(L);
for (size_t i = 0; i < Units::stress_cutoffs.size(); i++)
Lua::TableInsert(L, i, Units::stress_cutoffs[i]);
return 1;
}
static const luaL_Reg dfhack_units_funcs[] = { static const luaL_Reg dfhack_units_funcs[] = {
{ "getPosition", units_getPosition }, { "getPosition", units_getPosition },
{ "getNoblePositions", units_getNoblePositions }, { "getNoblePositions", units_getNoblePositions },
{ "getUnitsInBox", units_getUnitsInBox }, { "getUnitsInBox", units_getUnitsInBox },
{ "getStressCutoffs", units_getStressCutoffs },
{ NULL, NULL } { NULL, NULL }
}; };
@ -1744,6 +1772,11 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getItemBaseValue), WRAPM(Items, getItemBaseValue),
WRAPM(Items, getValue), WRAPM(Items, getValue),
WRAPM(Items, createItem), WRAPM(Items, createItem),
WRAPM(Items, checkMandates),
WRAPM(Items, canTrade),
WRAPM(Items, canTradeWithContents),
WRAPM(Items, isRouteVehicle),
WRAPM(Items, isSquadEquipment),
WRAPN(moveToGround, items_moveToGround), WRAPN(moveToGround, items_moveToGround),
WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToInventory, items_moveToInventory), WRAPN(moveToInventory, items_moveToInventory),
@ -1985,11 +2018,12 @@ static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, constructWithItems), WRAPM(Buildings, constructWithItems),
WRAPM(Buildings, constructWithFilters), WRAPM(Buildings, constructWithFilters),
WRAPM(Buildings, deconstruct), WRAPM(Buildings, deconstruct),
WRAPM(Buildings, markedForRemoval),
WRAPM(Buildings, getRoomDescription),
WRAPM(Buildings, isActivityZone), WRAPM(Buildings, isActivityZone),
WRAPM(Buildings, isPenPasture), WRAPM(Buildings, isPenPasture),
WRAPM(Buildings, isPitPond), WRAPM(Buildings, isPitPond),
WRAPM(Buildings, isActive), WRAPM(Buildings, isActive),
WRAPM(Buildings, markedForRemoval),
{ NULL, NULL } { NULL, NULL }
}; };
@ -2221,11 +2255,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;
@ -2429,6 +2459,15 @@ static const luaL_Reg dfhack_designations_funcs[] = {
{NULL, NULL} {NULL, NULL}
}; };
/***** Kitchen module *****/
static const LuaWrapper::FunctionReg dfhack_kitchen_module[] = {
WRAPM(Kitchen, findExclusion),
WRAPM(Kitchen, addExclusion),
WRAPM(Kitchen, removeExclusion),
{NULL, NULL}
};
/***** Console module *****/ /***** Console module *****/
namespace console { namespace console {
@ -2977,10 +3016,11 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs); OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs);
OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs); OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs);
OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs); OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs);
OpenModule(state, "constructions", dfhack_constructions_module); OpenModule(state, "constructions", dfhack_constructions_module, dfhack_constructions_funcs);
OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs); OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs);
OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs); OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs);
OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs); OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs);
OpenModule(state, "kitchen", dfhack_kitchen_module);
OpenModule(state, "console", dfhack_console_module); OpenModule(state, "console", dfhack_console_module);
OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs); OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs);
} }

@ -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,9 +293,15 @@ 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);
if (rv == Console::SHUTDOWN)
lua_pushstring(S, "shutdown requested");
else
lua_pushstring(S, "input error"); lua_pushstring(S, "input error");
return 2; return 2;
} }
@ -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);
{ {

@ -1076,20 +1076,8 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id
try { try {
id->invoke(state, 1); id->invoke(state, 1);
} }
catch (Error::NullPointer &e) { catch (Error::All &e) {
const char *vn = e.varname(); field_error(state, UPVAL_METHOD_NAME, e.what(), "invoke");
std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (Error::InvalidArgument &e) {
const char *vn = e.expr();
std::string tmp = stl_sprintf("Invalid argument; expected: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (Error::VTableMissing &e) {
const char *cn = e.name();
std::string tmp = stl_sprintf("Missing vtable address: %s", cn ? cn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
} }
catch (std::exception &e) { catch (std::exception &e) {
std::string tmp = stl_sprintf("C++ exception: %s", e.what()); std::string tmp = stl_sprintf("C++ exception: %s", e.what());
@ -1107,17 +1095,8 @@ int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *cont
try { try {
return fn(state); return fn(state);
} }
catch (Error::NullPointer &e) { catch (Error::All &e) {
const char *vn = e.varname(); return luaL_error(state, "%s: %s", context, e.what());
return luaL_error(state, "%s: NULL pointer: %s", context, vn ? vn : "?");
}
catch (Error::InvalidArgument &e) {
const char *vn = e.expr();
return luaL_error(state, "%s: Invalid argument; expected: %s", context, vn ? vn : "?");
}
catch (Error::VTableMissing &e) {
const char *cn = e.name();
return luaL_error(state, "%s: Missing vtable address: %s", context, cn ? cn : "?");
} }
catch (std::exception &e) { catch (std::exception &e) {
return luaL_error(state, "%s: C++ exception: %s", context, e.what()); return luaL_error(state, "%s: C++ exception: %s", context, e.what());

@ -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;
@ -1025,11 +1026,23 @@ static int meta_enum_attr_index(lua_State *state)
luaL_error(state, "Invalid index in enum.attrs[]"); luaL_error(state, "Invalid index in enum.attrs[]");
auto id = (enum_identity*)lua_touserdata(state, lua_upvalueindex(2)); auto id = (enum_identity*)lua_touserdata(state, lua_upvalueindex(2));
auto *complex = id->getComplex();
int64_t idx = lua_tonumber(state, 2); int64_t idx = lua_tonumber(state, 2);
if (complex)
{
auto it = complex->value_index_map.find(idx);
if (it != complex->value_index_map.end())
idx = int64_t(it->second);
else
idx = id->getLastItem() + 1;
}
else
{
if (idx < id->getFirstItem() || idx > id->getLastItem()) if (idx < id->getFirstItem() || idx > id->getLastItem())
idx = id->getLastItem()+1; idx = id->getLastItem()+1;
idx -= id->getFirstItem(); idx -= id->getFirstItem();
}
uint8_t *ptr = (uint8_t*)id->getAttrs(); uint8_t *ptr = (uint8_t*)id->getAttrs();
auto atype = id->getAttrType(); auto atype = id->getAttrType();
@ -1349,6 +1362,68 @@ static int wtype_next_item(lua_State *state)
return 1; return 1;
} }
/*
* Complex enums
*
* upvalues for all of these:
* 1: key table? unsure, taken from wtype stuff
* 2: enum_identity::ComplexData
*/
static bool complex_enum_next_item_helper(lua_State *L, int64_t &item, bool wrap = false)
{
const auto *complex = (enum_identity::ComplexData*)lua_touserdata(L, lua_upvalueindex(2));
auto it = complex->value_index_map.find(item);
if (it != complex->value_index_map.end())
{
size_t index = it->second;
if (!wrap && index >= complex->size() - 1)
return false;
item = complex->index_value_map[(index + 1) % complex->size()];
return true;
}
return false;
}
static int complex_enum_inext(lua_State *L)
{
bool is_first = lua_isuserdata(L, 2);
int64_t i = (is_first)
? ((enum_identity::ComplexData*)lua_touserdata(L, lua_upvalueindex(2)))->index_value_map[0]
: luaL_checkint(L, 2);
if (is_first || complex_enum_next_item_helper(L, i))
{
lua_pushinteger(L, i);
lua_rawgeti(L, lua_upvalueindex(1), i);
return 2;
}
else
{
lua_pushnil(L);
return 1;
}
}
static int complex_enum_next_item(lua_State *L)
{
int64_t cur = luaL_checkint(L, lua_gettop(L) > 1 ? 2 : 1); // 'self' optional
complex_enum_next_item_helper(L, cur, true);
lua_pushinteger(L, cur);
return 1;
}
static int complex_enum_ipairs(lua_State *L)
{
lua_pushvalue(L, lua_upvalueindex(1));
lua_pushvalue(L, lua_upvalueindex(2));
lua_pushcclosure(L, complex_enum_inext, 2);
lua_pushnil(L);
lua_pushlightuserdata(L, (void*)1);
return 3;
}
static void RenderTypeChildren(lua_State *state, const std::vector<compound_identity*> &children); static void RenderTypeChildren(lua_State *state, const std::vector<compound_identity*> &children);
void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name) void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name)
@ -1371,17 +1446,53 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit
int base = lua_gettop(state); int base = lua_gettop(state);
lua_newtable(state); lua_newtable(state);
auto *complex = eid->getComplex();
// For enums, set mapping between keys and values // For enums, set mapping between keys and values
if (complex)
{
for (size_t i = 0; i < complex->size(); i++)
{
if (keys[i])
AssociateId(state, base+1, complex->index_value_map[i], keys[i]);
}
}
else
{
for (int64_t i = eid->getFirstItem(), j = 0; i <= eid->getLastItem(); i++, j++) for (int64_t i = eid->getFirstItem(), j = 0; i <= eid->getLastItem(); i++, j++)
{ {
if (keys[j]) if (keys[j])
AssociateId(state, base+1, i, keys[j]); AssociateId(state, base+1, i, keys[j]);
} }
}
if (complex)
{
lua_pushvalue(state, base + 1);
lua_pushlightuserdata(state, (void*)complex);
lua_pushcclosure(state, complex_enum_ipairs, 2);
lua_setfield(state, ix_meta, "__ipairs");
lua_pushinteger(state, 0); // unused; to align ComplexData
lua_pushlightuserdata(state, (void*)complex);
lua_pushcclosure(state, complex_enum_next_item, 2);
lua_setfield(state, ftable, "next_item");
lua_pushinteger(state, eid->getFirstItem());
lua_setfield(state, ftable, "_first_item");
lua_pushinteger(state, eid->getLastItem());
lua_setfield(state, ftable, "_last_item");
lua_pushboolean(state, true);
lua_setfield(state, ftable, "_complex");
}
else
{
if (eid->getFirstItem() <= eid->getLastItem()) if (eid->getFirstItem() <= eid->getLastItem())
{ {
lua_pushvalue(state, base+1); lua_pushvalue(state, base + 1);
lua_pushinteger(state, eid->getFirstItem()-1); lua_pushinteger(state, eid->getFirstItem() - 1);
lua_pushinteger(state, eid->getLastItem()); lua_pushinteger(state, eid->getLastItem());
lua_pushcclosure(state, wtype_ipairs, 3); lua_pushcclosure(state, wtype_ipairs, 3);
lua_setfield(state, ix_meta, "__ipairs"); lua_setfield(state, ix_meta, "__ipairs");
@ -1396,6 +1507,10 @@ static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identit
lua_pushinteger(state, eid->getLastItem()); lua_pushinteger(state, eid->getLastItem());
lua_setfield(state, ftable, "_last_item"); lua_setfield(state, ftable, "_last_item");
lua_pushboolean(state, false);
lua_setfield(state, ftable, "_complex");
}
} }
SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN);

@ -25,9 +25,11 @@ distribution.
#include "Internal.h" #include "Internal.h"
#include "Export.h" #include "Export.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include "Error.h" #include "ColorText.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>
@ -37,21 +39,11 @@ distribution.
#include <ctype.h> #include <ctype.h>
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
#include <cstdlib>
#include <sstream> #include <sstream>
#include <map> #include <map>
#include <array>
const char *DFHack::Error::NullPointer::what() const throw() {
return "DFHack::Error::NullPointer";
}
const char *DFHack::Error::InvalidArgument::what() const throw() {
return "DFHack::Error::InvalidArgument";
}
const char *DFHack::Error::VTableMissing::what() const throw() {
return "DFHack::Error::VTableMissing";
}
std::string stl_sprintf(const char *fmt, ...) { std::string stl_sprintf(const char *fmt, ...) {
va_list lst; va_list lst;
@ -62,21 +54,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 (;;) { */
std::array<char,128> buf;
va_list args2; va_list args2;
va_copy(args2, args); va_copy(args2, args);
int rsz = vsnprintf(&buf[0], buf.size(), fmt, args2); int rsz = vsnprintf(&buf[0], buf.size(), fmt, args2);
va_end(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,
@ -399,3 +395,8 @@ DFHACK_EXPORT std::string DF2CONSOLE(const std::string &in)
#endif #endif
return is_utf ? DF2UTF(in) : in; return is_utf ? DF2UTF(in) : in;
} }
DFHACK_EXPORT std::string DF2CONSOLE(DFHack::color_ostream &out, const std::string &in)
{
return out.is_console() ? DF2CONSOLE(in) : in;
}

@ -23,6 +23,7 @@
*/ */
namespace DFHack namespace DFHack
{ {
DFLibrary* GLOBAL_NAMES = (DFLibrary*)RTLD_DEFAULT;
DFLibrary * OpenPlugin (const char * filename) DFLibrary * OpenPlugin (const char * filename)
{ {
dlerror(); dlerror();

@ -41,6 +41,7 @@ distribution.
*/ */
namespace DFHack namespace DFHack
{ {
DFLibrary* GLOBAL_NAMES = (DFLibrary*)GetModuleHandle(nullptr);
DFLibrary * OpenPlugin (const char * filename) DFLibrary * OpenPlugin (const char * filename)
{ {
return (DFLibrary *) LoadLibrary(filename); return (DFLibrary *) LoadLibrary(filename);

@ -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;
}; };
@ -276,6 +275,7 @@ bool Plugin::load(color_ostream &con)
plugin_check_symbol("plugin_name") plugin_check_symbol("plugin_name")
plugin_check_symbol("plugin_version") plugin_check_symbol("plugin_version")
plugin_check_symbol("plugin_abi_version")
plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_self")
plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_init")
plugin_check_symbol("plugin_globals") plugin_check_symbol("plugin_globals")
@ -287,11 +287,19 @@ bool Plugin::load(color_ostream &con)
return false; return false;
} }
const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version"); const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version");
const int *plugin_abi_version = (int*) LookupPlugin(plug, "plugin_abi_version");
const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "plugin_git_description"); const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "plugin_git_description");
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
const char *dfhack_version = Version::dfhack_version(); const char *dfhack_version = Version::dfhack_version();
const char *dfhack_git_desc = Version::git_description(); const char *dfhack_git_desc = Version::git_description();
const char *plug_git_desc = plug_git_desc_ptr ? *plug_git_desc_ptr : "unknown"; const char *plug_git_desc = plug_git_desc_ptr ? *plug_git_desc_ptr : "unknown";
if (*plugin_abi_version != Version::dfhack_abi_version())
{
con.printerr("Plugin %s: ABI version mismatch (Plugin: %i, DFHack: %i)\n",
*plug_name, *plugin_abi_version, Version::dfhack_abi_version());
plugin_abort_load;
return false;
}
if (strcmp(dfhack_version, *plug_version) != 0) if (strcmp(dfhack_version, *plug_version) != 0)
{ {
con.printerr("Plugin %s was not built for this version of DFHack.\n" con.printerr("Plugin %s was not built for this version of DFHack.\n"
@ -777,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;
} }
@ -797,6 +805,25 @@ PluginManager::~PluginManager()
void PluginManager::init() void PluginManager::init()
{ {
loadAll(); loadAll();
bool any_loaded = false;
for (auto p : all_plugins)
{
if (p.second->getState() == Plugin::PS_LOADED)
{
any_loaded = true;
break;
}
}
if (!any_loaded && !listPlugins().empty())
{
Core::printerr("\n"
"All plugins present failed to load.\n"
"If you are using Windows XP, this is probably due to a Visual Studio 2015 bug.\n"
"Windows XP is unsupported by Microsoft as of 2014, so we do not support it.\n\n"
"If this was unexpected and you are not using Windows XP, please report this.\n\n"
);
}
} }
bool PluginManager::addPlugin(string name) bool PluginManager::addPlugin(string name)

@ -208,8 +208,10 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
if (log_ranges) if (log_ranges)
{ {
fprintf(stderr, fprintf(stderr,
"%p-%p %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", "%p-%p %8zuK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n",
address, (address + vmsize), (vmsize >> 10), (void*)address,
(void*)(address + vmsize),
size_t(vmsize >> 10),
(info.protection & VM_PROT_READ) ? 'r' : '-', (info.protection & VM_PROT_READ) ? 'r' : '-',
(info.protection & VM_PROT_WRITE) ? 'w' : '-', (info.protection & VM_PROT_WRITE) ? 'w' : '-',
(info.protection & VM_PROT_EXECUTE) ? 'x' : '-', (info.protection & VM_PROT_EXECUTE) ? 'x' : '-',
@ -250,22 +252,6 @@ int Process::adjustOffset(int offset, bool /*to_file*/)
return offset; return offset;
} }
static int getdir (string dir, vector<string> &files)
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
cout << "Error(" << errno << ") opening " << dir << endl;
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
files.push_back(string(dirp->d_name));
}
closedir(dp);
return 0;
}
uint32_t Process::getTickCount() uint32_t Process::getTickCount()
{ {
struct timeval tp; struct timeval tp;
@ -289,6 +275,11 @@ string Process::getPath()
if (_NSGetExecutablePath(path, &size) == 0) { if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL); real_path = realpath(path, NULL);
} }
else {
fprintf(stderr, "_NSGetExecutablePath failed!\n");
cached_path = ".";
return cached_path;
}
std::string path_string(real_path); std::string path_string(real_path);
int last_slash = path_string.find_last_of("/"); int last_slash = path_string.find_last_of("/");
cached_path = path_string.substr(0,last_slash); cached_path = path_string.substr(0,last_slash);

@ -176,22 +176,6 @@ int Process::adjustOffset(int offset, bool /*to_file*/)
return offset; return offset;
} }
static int getdir (string dir, vector<string> &files)
{
DIR *dp;
struct dirent *dirp;
if((dp = opendir(dir.c_str())) == NULL)
{
cout << "Error(" << errno << ") opening " << dir << endl;
return errno;
}
while ((dirp = readdir(dp)) != NULL) {
files.push_back(string(dirp->d_name));
}
closedir(dp);
return 0;
}
uint32_t Process::getTickCount() uint32_t Process::getTickCount()
{ {
struct timeval tp; struct timeval tp;

@ -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;
} }
@ -215,7 +236,7 @@ void RemoteClient::disconnect()
} }
bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function,
const std::string &name, const std::string &proto) const std::string &name, const std::string &plugin)
{ {
if (!active || !socket->IsSocketValid()) if (!active || !socket->IsSocketValid())
return false; return false;
@ -226,8 +247,8 @@ bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function,
auto in = bind_call.in(); auto in = bind_call.in();
in->set_method(name); in->set_method(name);
if (!proto.empty()) if (!plugin.empty())
in->set_plugin(proto); in->set_plugin(plugin);
in->set_input_msg(function->p_in_template->GetTypeName()); in->set_input_msg(function->p_in_template->GetTypeName());
in->set_output_msg(function->p_out_template->GetTypeName()); in->set_output_msg(function->p_out_template->GetTypeName());
} }
@ -305,23 +326,23 @@ void RPCFunctionBase::reset(bool free)
} }
bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client, bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client,
const std::string &name, const std::string &proto) const std::string &name, const std::string &plugin)
{ {
if (isValid()) if (isValid())
{ {
if (p_client == client && this->name == name && this->proto == proto) if (p_client == client && this->name == name && this->plugin == plugin)
return true; return true;
out.printerr("Function already bound to %s::%s\n", out.printerr("Function already bound to %s::%s\n",
this->proto.c_str(), this->name.c_str()); this->plugin.c_str(), this->name.c_str());
return false; return false;
} }
this->name = name; this->name = name;
this->proto = proto; this->plugin = plugin;
this->p_client = client; this->p_client = client;
return client->bind(out, this, name, proto); return client->bind(out, this, name, plugin);
} }
bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg, bool size_ready) bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg, bool size_ready)
@ -350,14 +371,14 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (!isValid()) if (!isValid())
{ {
out.printerr("Calling an unbound RPC function %s::%s.\n", out.printerr("Calling an unbound RPC function %s::%s.\n",
this->proto.c_str(), this->name.c_str()); this->plugin.c_str(), this->name.c_str());
return CR_NOT_IMPLEMENTED; return CR_NOT_IMPLEMENTED;
} }
if (!p_client->socket->IsSocketValid()) if (!p_client->socket->IsSocketValid())
{ {
out.printerr("In call to %s::%s: invalid socket.\n", out.printerr("In call to %s::%s: invalid socket.\n",
this->proto.c_str(), this->name.c_str()); this->plugin.c_str(), this->name.c_str());
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
@ -366,14 +387,14 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (send_size > RPCMessageHeader::MAX_MESSAGE_SIZE) if (send_size > RPCMessageHeader::MAX_MESSAGE_SIZE)
{ {
out.printerr("In call to %s::%s: message too large: %d.\n", out.printerr("In call to %s::%s: message too large: %d.\n",
this->proto.c_str(), this->name.c_str(), send_size); this->plugin.c_str(), this->name.c_str(), send_size);
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
if (!sendRemoteMessage(p_client->socket, id, input, true)) if (!sendRemoteMessage(p_client->socket, id, input, true))
{ {
out.printerr("In call to %s::%s: I/O error in send.\n", out.printerr("In call to %s::%s: I/O error in send.\n",
this->proto.c_str(), this->name.c_str()); this->plugin.c_str(), this->name.c_str());
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
@ -388,7 +409,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (!readFullBuffer(p_client->socket, &header, sizeof(header))) if (!readFullBuffer(p_client->socket, &header, sizeof(header)))
{ {
out.printerr("In call to %s::%s: I/O error in receive header.\n", out.printerr("In call to %s::%s: I/O error in receive header.\n",
this->proto.c_str(), this->name.c_str()); this->plugin.c_str(), this->name.c_str());
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
@ -400,7 +421,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE)
{ {
out.printerr("In call to %s::%s: invalid received size %d.\n", out.printerr("In call to %s::%s: invalid received size %d.\n",
this->proto.c_str(), this->name.c_str(), header.size); this->plugin.c_str(), this->name.c_str(), header.size);
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
@ -409,7 +430,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (!readFullBuffer(p_client->socket, buf, header.size)) if (!readFullBuffer(p_client->socket, buf, header.size))
{ {
out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n", out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n",
this->proto.c_str(), this->name.c_str(), header.size); this->plugin.c_str(), this->name.c_str(), header.size);
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
@ -418,7 +439,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
if (!output->ParseFromArray(buf, header.size)) if (!output->ParseFromArray(buf, header.size))
{ {
out.printerr("In call to %s::%s: error parsing received result.\n", out.printerr("In call to %s::%s: error parsing received result.\n",
this->proto.c_str(), this->name.c_str()); this->plugin.c_str(), this->name.c_str());
delete[] buf; delete[] buf;
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
@ -432,7 +453,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
text_decoder.decode(&text_data); text_decoder.decode(&text_data);
else else
out.printerr("In call to %s::%s: received invalid text data.\n", out.printerr("In call to %s::%s: received invalid text data.\n",
this->proto.c_str(), this->name.c_str()); this->plugin.c_str(), this->name.c_str());
break; break;
default: default:

@ -58,9 +58,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;
@ -108,6 +109,24 @@ void RPCService::finalize(ServerConnection *owner, std::vector<ServerFunctionBas
} }
} }
void RPCService::dumpMethods(std::ostream & out) const
{
for (auto fn : functions)
{
std::string in_name = fn->p_in_template->GetTypeName();
size_t last_dot = in_name.rfind('.');
if (last_dot != std::string::npos)
in_name = in_name.substr(last_dot + 1);
std::string out_name = fn->p_out_template->GetTypeName();
last_dot = out_name.rfind('.');
if (last_dot != std::string::npos)
out_name = out_name.substr(last_dot + 1);
out << "// RPC " << fn->name << " : " << in_name << " -> " << out_name << endl;
}
}
ServerConnection::ServerConnection(CActiveSocket *socket) ServerConnection::ServerConnection(CActiveSocket *socket)
: socket(socket), stream(this) : socket(socket), stream(this)
{ {
@ -284,7 +303,11 @@ void ServerConnection::threadFn()
} }
else else
{ {
if (!fn->in()->ParseFromArray(buf.get(), header.size)) if (((fn->flags & SF_ALLOW_REMOTE) != SF_ALLOW_REMOTE) && strcmp(socket->GetClientAddr(), "127.0.0.1") != 0)
{
stream.printerr("In call to %s: forbidden host: %s\n", fn->name, socket->GetClientAddr());
}
else if (!fn->in()->ParseFromArray(buf.get(), header.size))
{ {
stream.printerr("In call to %s: could not decode input args.\n", fn->name); stream.printerr("In call to %s: could not decode input args.\n", fn->name);
} }
@ -375,8 +398,45 @@ bool ServerMain::listen(int port)
socket->Initialize(); socket->Initialize();
std::string filename("dfhack-config/remote-server.json");
Json::Value configJson;
std::ifstream inFile(filename, std::ios_base::in);
bool allow_remote = false;
if (inFile.is_open())
{
inFile >> configJson;
inFile.close();
allow_remote = configJson.get("allow_remote", "false").asBool();
}
// rewrite/normalize config file
configJson["allow_remote"] = allow_remote;
configJson["port"] = configJson.get("port", RemoteClient::DEFAULT_PORT);
std::ofstream outFile(filename, std::ios_base::trunc);
if (outFile.is_open())
{
outFile << configJson;
outFile.close();
}
std::cerr << "Listening on port " << port << (allow_remote ? " (remote enabled)" : "") << std::endl;
if (allow_remote)
{
if (!socket->Listen(NULL, port))
return false;
}
else
{
if (!socket->Listen("127.0.0.1", port)) if (!socket->Listen("127.0.0.1", port))
return false; return false;
}
thread = new tthread::thread(threadFn, this); thread = new tthread::thread(threadFn, this);
thread->detach(); thread->detach();

@ -395,6 +395,7 @@ static command_result GetWorldInfo(color_ostream &stream,
{ {
case game_type::DWARF_MAIN: case game_type::DWARF_MAIN:
case game_type::DWARF_RECLAIM: case game_type::DWARF_RECLAIM:
case game_type::DWARF_UNRETIRE:
out->set_mode(GetWorldInfoOut::MODE_DWARF); out->set_mode(GetWorldInfoOut::MODE_DWARF);
out->set_civ_id(ui->civ_id); out->set_civ_id(ui->civ_id);
out->set_site_id(ui->site_id); out->set_site_id(ui->site_id);
@ -403,6 +404,7 @@ static command_result GetWorldInfo(color_ostream &stream,
break; break;
case game_type::ADVENTURE_MAIN: case game_type::ADVENTURE_MAIN:
case game_type::ADVENTURE_ARENA:
out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); out->set_mode(GetWorldInfoOut::MODE_ADVENTURE);
if (auto unit = vector_get(world->units.active, 0)) if (auto unit = vector_get(world->units.active, 0))
@ -588,6 +590,8 @@ static command_result ListUnits(color_ostream &stream,
{ {
auto unit = vec[i]; auto unit = vec[i];
if (!Units::isActive(unit) && !Units::isKilled(unit))
continue;
if (in->has_race() && unit->race != in->race()) if (in->has_race() && unit->race != in->race())
continue; continue;
if (in->has_civ_id() && unit->civ_id != in->civ_id()) if (in->has_civ_id() && unit->civ_id != in->civ_id())
@ -648,39 +652,40 @@ 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); addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addMethod("RunCommand", &CoreService::RunCommand, SF_DONT_SUSPEND); addMethod("RunCommand", &CoreService::RunCommand, SF_DONT_SUSPEND);
// Add others here: // Add others here:
addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND); addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND); addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addMethod("RunLua", &CoreService::RunLua); addMethod("RunLua", &CoreService::RunLua);
// Functions: // Functions:
addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND); addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND); addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("GetWorldInfo", GetWorldInfo); addFunction("GetWorldInfo", GetWorldInfo, SF_ALLOW_REMOTE);
addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND); addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND); addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE | SF_ALLOW_REMOTE);
addFunction("ListUnits", ListUnits); addFunction("ListUnits", ListUnits, SF_ALLOW_REMOTE);
addFunction("ListSquads", ListSquads); addFunction("ListSquads", ListSquads, SF_ALLOW_REMOTE);
addFunction("SetUnitLabors", SetUnitLabors); addFunction("SetUnitLabors", SetUnitLabors, SF_ALLOW_REMOTE);
} }
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,
@ -721,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;
} }
@ -731,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;
} }

@ -35,6 +35,7 @@ using namespace std;
#include "VersionInfo.h" #include "VersionInfo.h"
#include "Error.h" #include "Error.h"
#include "Memory.h" #include "Memory.h"
#include "PluginManager.h"
using namespace DFHack; using namespace DFHack;
#include <tinyxml.h> #include <tinyxml.h>
@ -133,18 +134,30 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
if(!cstr_key) if(!cstr_key)
throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name);
const char *cstr_value = pMemEntry->Attribute("value"); const char *cstr_value = pMemEntry->Attribute("value");
if(!cstr_value) const char *cstr_mangled = pMemEntry->Attribute("mangled");
if(!cstr_value && !cstr_mangled)
{ {
cerr << "Dummy symbol table entry: " << cstr_key << endl; cerr << "Dummy symbol table entry: " << cstr_key << endl;
continue; continue;
} }
if ((is_vtable && no_vtables) || (!is_vtable && no_globals)) if ((is_vtable && no_vtables) || (!is_vtable && no_globals))
continue; continue;
#ifdef DFHACK64 uintptr_t addr;
uintptr_t addr = strtoull(cstr_value, 0, 0); if (cstr_value) {
#else if (sizeof(addr) == sizeof(unsigned long))
uintptr_t addr = strtol(cstr_value, 0, 0); addr = strtoul(cstr_value, 0, 0);
#endif else
addr = strtoull(cstr_value, 0, 0);
} else {
addr = (uintptr_t)DFHack::LookupPlugin(DFHack::GLOBAL_NAMES, cstr_mangled);
if (!addr)
continue;
const char *cstr_offset = pMemEntry->Attribute("offset");
if (cstr_offset) {
unsigned long offset = strtoul(cstr_offset, 0, 0);
addr += offset;
}
}
if (is_vtable) if (is_vtable)
mem->setVTable(cstr_key, addr); mem->setVTable(cstr_key, addr);
else else
@ -153,7 +166,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
else if (type == "md5-hash") else if (type == "md5-hash")
{ {
const char *cstr_value = pMemEntry->Attribute("value"); const char *cstr_value = pMemEntry->Attribute("value");
fprintf(stderr, "%s: MD5: %s\n", cstr_name, cstr_value); fprintf(stderr, "%s (%s): MD5: %s\n", cstr_name, cstr_os, cstr_value);
if(!cstr_value) if(!cstr_value)
throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name);
mem->addMD5(cstr_value); mem->addMD5(cstr_value);
@ -161,7 +174,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
else if (type == "binary-timestamp") else if (type == "binary-timestamp")
{ {
const char *cstr_value = pMemEntry->Attribute("value"); const char *cstr_value = pMemEntry->Attribute("value");
fprintf(stderr, "%s: PE: %s\n", cstr_name, cstr_value); fprintf(stderr, "%s (%s): PE: %s\n", cstr_name, cstr_os, cstr_value);
if(!cstr_value) if(!cstr_value)
throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name); throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name);
mem->addPE(strtol(cstr_value, 0, 16)); mem->addPE(strtol(cstr_value, 0, 16));

@ -1,51 +0,0 @@
if(NOT EXISTS ${dfhack_SOURCE_DIR}/.git/index OR NOT EXISTS ${dfhack_SOURCE_DIR}/.git/modules/library/xml/index)
MESSAGE(FATAL_ERROR "Could not find git index file(s)")
endif()
set(git_describe_tmp_h ${dfhack_SOURCE_DIR}/library/include/git-describe.tmp.h)
set(git_describe_h ${dfhack_SOURCE_DIR}/library/include/git-describe.h)
if(EXISTS ${git_describe_tmp_h} AND
NOT(${dfhack_SOURCE_DIR}/.git/index IS_NEWER_THAN ${git_describe_tmp_h}) AND
NOT(${dfhack_SOURCE_DIR}/.git/modules/library/xml/index IS_NEWER_THAN ${git_describe_tmp_h}))
return()
endif()
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=8 --long
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_COMMIT)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=8 --exact-match
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
RESULT_VARIABLE DFHACK_GIT_TAGGED_RESULT
OUTPUT_QUIET ERROR_QUIET)
# Check library/xml for matching commits
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD:library/xml
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_XML_EXPECTED_COMMIT)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}/library/xml"
OUTPUT_VARIABLE DFHACK_GIT_XML_COMMIT)
file(WRITE ${git_describe_tmp_h} "")
macro(git_describe_definition var)
string(STRIP "${${var}}" GIT_DEF_VALUE)
file(APPEND ${git_describe_tmp_h} "#define ${var} \"${GIT_DEF_VALUE}\"\n")
endmacro()
git_describe_definition(DFHACK_GIT_DESCRIPTION)
git_describe_definition(DFHACK_GIT_COMMIT)
git_describe_definition(DFHACK_GIT_XML_EXPECTED_COMMIT)
git_describe_definition(DFHACK_GIT_XML_COMMIT)
if(${DFHACK_GIT_TAGGED_RESULT} EQUAL 0)
file(APPEND ${git_describe_tmp_h} "#define DFHACK_GIT_TAGGED\n")
endif()
if(${DFHACK_GIT_XML_COMMIT} STREQUAL ${DFHACK_GIT_XML_EXPECTED_COMMIT})
file(APPEND ${git_describe_tmp_h} "#define DFHACK_GIT_XML_MATCH\n")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
${git_describe_tmp_h} ${git_describe_h})

@ -0,0 +1,39 @@
# Generated from git-describe.cmake.in
set(DFHACK_BUILD_ID "@DFHACK_BUILD_ID@")
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=8 --long
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_DESCRIPTION
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=8 --exact-match
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
RESULT_VARIABLE DFHACK_GIT_TAGGED_RESULT
OUTPUT_QUIET ERROR_QUIET)
# Check library/xml for matching commits
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD:library/xml
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}"
OUTPUT_VARIABLE DFHACK_GIT_XML_EXPECTED_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
WORKING_DIRECTORY "${dfhack_SOURCE_DIR}/library/xml"
OUTPUT_VARIABLE DFHACK_GIT_XML_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(${DFHACK_GIT_TAGGED_RESULT} EQUAL 0)
set(DFHACK_GIT_TAGGED TRUE)
else()
set(DFHACK_GIT_TAGGED FALSE)
endif()
if(${DFHACK_GIT_XML_COMMIT} STREQUAL ${DFHACK_GIT_XML_EXPECTED_COMMIT})
set(DFHACK_GIT_XML_MATCH TRUE)
else()
set(DFHACK_GIT_XML_MATCH FALSE)
endif()
configure_file(${dfhack_SOURCE_DIR}/library/include/git-describe.h.in ${dfhack_SOURCE_DIR}/library/include/git-describe.h)

@ -1 +1 @@
git-describe.* git-describe.h

@ -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();
}
CoreSuspender(Core* core, std::defer_lock_t) :
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 *core, bool base = false) : core(core) {
level = core->ClaimSuspend(base); ~CoreSuspender() {
if (owns_lock())
unlock();
} }
~CoreSuspendClaimer() { core->DisclaimSuspend(level); }
}; };
/*!
* 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;
} }

@ -1,14 +1,18 @@
#pragma once #pragma once
namespace DFHack { namespace DFHack {
namespace Version { namespace Version {
const char *dfhack_version();
const char *df_version(); const char *df_version();
const char *dfhack_version();
const char *dfhack_release(); const char *dfhack_release();
const char *dfhack_build_id();
int dfhack_abi_version();
const char *git_description(); const char *git_description();
const char *git_commit(); const char *git_commit();
const char *git_xml_commit(); const char *git_xml_commit();
const char *git_xml_expected_commit(); const char *git_xml_expected_commit();
bool git_xml_match(); bool git_xml_match();
bool is_release(); bool is_release();
bool is_prerelease(); bool is_prerelease();
} }
@ -16,13 +20,17 @@ namespace DFHack {
#ifndef NO_DFHACK_VERSION_MACROS #ifndef NO_DFHACK_VERSION_MACROS
#define DF_VERSION (DFHack::Version::df_version()) #define DF_VERSION (DFHack::Version::df_version())
#define DFHACK_RELEASE (DFHack::Version::dfhack_release())
#define DFHACK_VERSION (DFHack::Version::dfhack_version()) #define DFHACK_VERSION (DFHack::Version::dfhack_version())
#define DFHACK_RELEASE (DFHack::Version::dfhack_release())
#define DFHACK_BUILD_ID (DFHack::Version::dfhack_build_id())
#define DFHACK_ABI_VERSION (DFHack::Version::dfhack_abi_version())
#define DFHACK_GIT_DESCRIPTION (DFHack::Version::git_description()) #define DFHACK_GIT_DESCRIPTION (DFHack::Version::git_description())
#define DFHACK_GIT_COMMIT (DFHack::Version::git_commit()) #define DFHACK_GIT_COMMIT (DFHack::Version::git_commit())
#define DFHACK_GIT_XML_COMMIT (DFHack::Version::git_xml_commit()) #define DFHACK_GIT_XML_COMMIT (DFHack::Version::git_xml_commit())
#define DFHACK_GIT_XML_EXPECTED_COMMIT (DFHack::Version::git_xml_expected_commit()) #define DFHACK_GIT_XML_EXPECTED_COMMIT (DFHack::Version::git_xml_expected_commit())
#define DFHACK_GIT_XML_MATCH (DFHack::Version::git_xml_match()) #define DFHACK_GIT_XML_MATCH (DFHack::Version::git_xml_match())
#define DFHACK_IS_RELEASE (DFHack::Version::is_release()) #define DFHACK_IS_RELEASE (DFHack::Version::is_release())
#define DFHACK_IS_PRERELEASE (DFHack::Version::is_prerelease()) #define DFHACK_IS_PRERELEASE (DFHack::Version::is_prerelease())
#endif #endif

@ -24,11 +24,13 @@ distribution.
#pragma once #pragma once
#include <string>
#include <sstream>
#include <vector>
#include <map> #include <map>
#include <set> #include <set>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "Core.h" #include "Core.h"
#include "BitArray.h" #include "BitArray.h"
@ -188,9 +190,22 @@ namespace DFHack
class struct_identity; class struct_identity;
class DFHACK_EXPORT enum_identity : public compound_identity { class DFHACK_EXPORT enum_identity : public compound_identity {
public:
struct ComplexData {
std::map<int64_t, size_t> value_index_map;
std::vector<int64_t> index_value_map;
ComplexData(std::initializer_list<int64_t> values);
size_t size() const {
return index_value_map.size();
}
};
private:
const char *const *keys; const char *const *keys;
const ComplexData *complex;
int64_t first_item_value; int64_t first_item_value;
int64_t last_item_value; int64_t last_item_value;
int count;
type_identity *base_type; type_identity *base_type;
@ -209,14 +224,16 @@ namespace DFHack
type_identity *base_type, type_identity *base_type,
int64_t first_item_value, int64_t last_item_value, int64_t first_item_value, int64_t last_item_value,
const char *const *keys, const char *const *keys,
const ComplexData *complex,
const void *attrs, struct_identity *attr_type); const void *attrs, struct_identity *attr_type);
virtual identity_type type() { return IDTYPE_ENUM; } virtual identity_type type() { return IDTYPE_ENUM; }
int64_t getFirstItem() { return first_item_value; } int64_t getFirstItem() { return first_item_value; }
int64_t getLastItem() { return last_item_value; } int64_t getLastItem() { return last_item_value; }
int getCount() { return int(last_item_value-first_item_value+1); } int getCount() { return count; }
const char *const *getKeys() { return keys; } const char *const *getKeys() { return keys; }
const ComplexData *getComplex() { return complex; }
type_identity *getBaseType() { return base_type; } type_identity *getBaseType() { return base_type; }
const void *getAttrs() { return attrs; } const void *getAttrs() { return attrs; }
@ -422,13 +439,17 @@ 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 ignored "-Wdelete-non-virtual-dtor"
template<class T> template<class T>
void *allocator_fn(void *out, const void *in) { void *allocator_fn(void *out, const void *in) {
if (out) { *(T*)out = *(const T*)in; return out; } if (out) { *(T*)out = *(const T*)in; return out; }
else if (in) { delete (T*)in; return (T*)in; } else if (in) { delete (T*)in; return (T*)in; }
else return new T(); else return new T();
} }
#pragma GCC diagnostic pop
template<class T> template<class T>
void *allocator_nodel_fn(void *out, const void *in) { void *allocator_nodel_fn(void *out, const void *in) {
@ -496,33 +517,100 @@ namespace DFHack {
*/ */
/** /**
* Return the next item in the enum, wrapping to the first one at the end. * Return the next item in the enum, wrapping to the first one at the end if 'wrap' is true (otherwise an invalid item).
*/ */
template<class T> template<class T>
inline typename df::enum_traits<T>::enum_type next_enum_item(T v) { inline typename std::enable_if<
!df::enum_traits<T>::is_complex,
typename df::enum_traits<T>::enum_type
>::type next_enum_item(T v, bool wrap = true)
{
typedef df::enum_traits<T> traits; typedef df::enum_traits<T> traits;
typedef typename traits::base_type base_type; typedef typename traits::base_type base_type;
base_type iv = base_type(v); base_type iv = base_type(v);
return (iv < traits::last_item_value) ? T(iv+1) : traits::first_item; if (iv < traits::last_item_value)
{
return T(iv + 1);
}
else
{
if (wrap)
return traits::first_item;
else
return T(traits::last_item_value + 1);
}
}
template<class T>
inline typename std::enable_if<
df::enum_traits<T>::is_complex,
typename df::enum_traits<T>::enum_type
>::type next_enum_item(T v, bool wrap = true)
{
typedef df::enum_traits<T> traits;
const auto &complex = traits::complex;
const auto it = complex.value_index_map.find(v);
if (it != complex.value_index_map.end())
{
if (!wrap && it->second + 1 == complex.size())
{
return T(traits::last_item_value + 1);
}
size_t next_index = (it->second + 1) % complex.size();
return T(complex.index_value_map[next_index]);
}
else
return T(traits::last_item_value + 1);
} }
/** /**
* Check if the value is valid for its enum type. * Check if the value is valid for its enum type.
*/ */
template<class T> template<class T>
inline bool is_valid_enum_item(T v) { inline typename std::enable_if<
!df::enum_traits<T>::is_complex,
bool
>::type is_valid_enum_item(T v)
{
return df::enum_traits<T>::is_valid(v); return df::enum_traits<T>::is_valid(v);
} }
template<class T>
inline typename std::enable_if<
df::enum_traits<T>::is_complex,
bool
>::type is_valid_enum_item(T v)
{
const auto &complex = df::enum_traits<T>::complex;
return complex.value_index_map.find(v) != complex.value_index_map.end();
}
/** /**
* Return the enum item key string pointer, or NULL if none. * Return the enum item key string pointer, or NULL if none.
*/ */
template<class T> template<class T>
inline const char *enum_item_raw_key(T val) { inline typename std::enable_if<
!df::enum_traits<T>::is_complex,
const char *
>::type enum_item_raw_key(T val) {
typedef df::enum_traits<T> traits; typedef df::enum_traits<T> traits;
return traits::is_valid(val) ? traits::key_table[(short)val - traits::first_item_value] : NULL; return traits::is_valid(val) ? traits::key_table[(short)val - traits::first_item_value] : NULL;
} }
template<class T>
inline typename std::enable_if<
df::enum_traits<T>::is_complex,
const char *
>::type enum_item_raw_key(T val) {
typedef df::enum_traits<T> traits;
const auto &value_index_map = traits::complex.value_index_map;
auto it = value_index_map.find(val);
if (it != value_index_map.end())
return traits::key_table[it->second];
else
return NULL;
}
/** /**
* Return the enum item key string pointer, or "?" if none. * Return the enum item key string pointer, or "?" if none.
*/ */
@ -700,7 +788,7 @@ namespace DFHack {
#define ENUM_NEXT_ITEM(enum,val) \ #define ENUM_NEXT_ITEM(enum,val) \
(DFHack::next_enum_item<df::enum>(val)) (DFHack::next_enum_item<df::enum>(val))
#define FOR_ENUM_ITEMS(enum,iter) \ #define FOR_ENUM_ITEMS(enum,iter) \
for(df::enum iter = ENUM_FIRST_ITEM(enum); is_valid_enum_item(iter); iter = df::enum(1+int(iter))) for(df::enum iter = ENUM_FIRST_ITEM(enum); DFHack::is_valid_enum_item(iter); iter = DFHack::next_enum_item(iter, false))
/* /*
* Include mandatory generated headers. * Include mandatory generated headers.

@ -24,11 +24,13 @@ distribution.
#pragma once #pragma once
#include <exception>
#include <sstream>
#include <string>
#include "Export.h" #include "Export.h"
#include "MiscUtils.h"
#include "Pragma.h" #include "Pragma.h"
#include <string>
#include <sstream>
#include <exception>
namespace DFHack namespace DFHack
{ {
@ -41,118 +43,91 @@ namespace DFHack
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma push #pragma push
/** /**
* C4275 is - The warning officially is non dll-interface class 'std::exception' used as base for * C4275 - The warning officially is non dll-interface class 'std::exception' used as base for
* dll-interface class * dll-interface class
* *
* Basically, its saying that you might have an ABI problem if you mismatch compilers. We don't * Basically, it's saying that you might have an ABI problem if you mismatch compilers. We don't
* care since we build all of DFhack at once against whatever Toady is using * care since we build all of DFhack at once against whatever Toady is using
*/ */
#pragma warning(disable: 4275) #pragma warning(disable: 4275)
#endif #endif
class DFHACK_EXPORT All : public std::exception{}; class DFHACK_EXPORT All : public std::exception
{
public:
const std::string full;
All(const std::string &full)
:full(full)
{}
virtual const char *what() const noexcept
{
return full.c_str();
}
virtual ~All() noexcept {}
};
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma pop #pragma pop
#endif #endif
class DFHACK_EXPORT NullPointer : public All { class DFHACK_EXPORT NullPointer : public All {
const char *varname_;
public: public:
NullPointer(const char *varname_ = NULL) : varname_(varname_) {} const char *const varname;
const char *varname() const { return varname_; } NullPointer(const char *varname = NULL, const char *func = NULL);
virtual const char *what() const throw();
}; };
#define CHECK_NULL_POINTER(var) \ #define CHECK_NULL_POINTER(var) \
{ if (var == NULL) throw DFHack::Error::NullPointer(#var); } { if (var == NULL) throw DFHack::Error::NullPointer(#var, DFHACK_FUNCTION_SIG); }
class DFHACK_EXPORT InvalidArgument : public All { class DFHACK_EXPORT InvalidArgument : public All {
const char *expr_;
public: public:
InvalidArgument(const char *expr_ = NULL) : expr_(expr_) {} const char *const expr;
const char *expr() const { return expr_; } InvalidArgument(const char *expr = NULL, const char *func = NULL);
virtual const char *what() const throw();
}; };
#define CHECK_INVALID_ARGUMENT(expr) \ #define CHECK_INVALID_ARGUMENT(expr) \
{ if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); } { if (!(expr)) throw DFHack::Error::InvalidArgument(#expr, DFHACK_FUNCTION_SIG); }
class DFHACK_EXPORT VTableMissing : public All { class DFHACK_EXPORT VTableMissing : public All {
const char *name_;
public: public:
VTableMissing(const char *name_ = NULL) : name_(name_) {} const char *const name;
const char *name() const { return name_; } VTableMissing(const char *name = NULL);
virtual const char *what() const throw();
}; };
class DFHACK_EXPORT AllSymbols : public All{}; class DFHACK_EXPORT AllSymbols : public All
{
public:
AllSymbols(const std::string &full)
:All(full)
{}
};
// Syntax errors and whatnot, the xml can't be read // Syntax errors and whatnot, the xml can't be read
class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols
{ {
public: public:
SymbolsXmlParse(const char* _desc, int _id, int _row, int _col) SymbolsXmlParse(const char* desc, int id, int row, int col);
:desc(_desc), id(_id), row(_row), col(_col)
{
std::stringstream s;
s << "error " << id << ": " << desc << ", at row " << row << " col " << col;
full = s.str();
}
std::string full;
const std::string desc; const std::string desc;
const int id; const int id;
const int row; const int row;
const int col; const int col;
virtual ~SymbolsXmlParse() throw(){};
virtual const char* what() const throw()
{
return full.c_str();
}
}; };
class DFHACK_EXPORT SymbolsXmlBadAttribute : public All class DFHACK_EXPORT SymbolsXmlBadAttribute : public AllSymbols
{ {
public: public:
SymbolsXmlBadAttribute(const char* _attr) : attr(_attr) SymbolsXmlBadAttribute(const char* attr);
{
std::stringstream s;
s << "attribute is either missing or invalid: " << attr;
full = s.str();
}
std::string full;
std::string attr; std::string attr;
virtual ~SymbolsXmlBadAttribute() throw(){};
virtual const char* what() const throw()
{
return full.c_str();
}
}; };
class DFHACK_EXPORT SymbolsXmlNoRoot : public All class DFHACK_EXPORT SymbolsXmlNoRoot : public AllSymbols
{ {
public: public:
SymbolsXmlNoRoot() {} SymbolsXmlNoRoot();
virtual ~SymbolsXmlNoRoot() throw(){};
virtual const char* what() const throw()
{
return "Symbol file is missing root element.";
}
}; };
class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public All class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public AllSymbols
{ {
public: public:
SymbolsXmlUnderspecifiedEntry(const char * _where) : where(_where) SymbolsXmlUnderspecifiedEntry(const char *where);
{
std::stringstream s;
s << "Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: " << where;
full = s.str();
}
virtual ~SymbolsXmlUnderspecifiedEntry() throw(){};
std::string where; std::string where;
std::string full;
virtual const char* what() const throw()
{
return full.c_str();
}
}; };
} }
} }

@ -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,11 +31,46 @@ 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;
using std::endl; using std::endl;
#if defined(_MSC_VER)
#define DFHACK_FUNCTION_SIG __FUNCSIG__
#elif defined(__GNUC__)
#define DFHACK_FUNCTION_SIG __PRETTY_FUNCTION__
#else
#define DFHACK_FUNCTION_SIG __func__
#endif
namespace DFHack {
class color_ostream;
}
/*! \namespace dts
* std.reverse() == dts, The namespace that include forward compatible helpers
* which can be used from newer standards. The preprocessor check prefers
* standard version if one is available. The standard version gets imported with
* using.
*/
namespace dts {
// Check if lib supports the feature test macro or version is over c++14.
#if __cpp_lib_make_unique < 201304 && __cplusplus < 201402L
//! Insert c++14 make_unique to be forward compatible. Array versions are
//! missing
template<typename 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 )
{ {
@ -346,10 +381,11 @@ 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);
DFHACK_EXPORT std::string DF2UTF(const std::string &in); DFHACK_EXPORT std::string DF2UTF(const std::string &in);
DFHACK_EXPORT std::string DF2CONSOLE(const std::string &in); DFHACK_EXPORT std::string DF2CONSOLE(const std::string &in);
DFHACK_EXPORT std::string DF2CONSOLE(DFHack::color_ostream &out, const std::string &in);

@ -61,11 +61,14 @@ namespace DFHack
namespace Version { namespace Version {
const char *dfhack_version(); const char *dfhack_version();
const char *git_description(); const char *git_description();
int dfhack_abi_version();
} }
// anon type, pretty much // anon type, pretty much
struct DFLibrary; struct DFLibrary;
// DFLibrary* that can be used to resolve global names
extern DFLibrary* GLOBAL_NAMES;
// Open a plugin library // Open a plugin library
DFHACK_EXPORT DFLibrary * OpenPlugin (const char * filename); DFHACK_EXPORT DFLibrary * OpenPlugin (const char * filename);
// find a symbol inside plugin // find a symbol inside plugin
@ -296,7 +299,8 @@ namespace DFHack
DFhackDataExport const char * plugin_name = m_plugin_name;\ DFhackDataExport const char * plugin_name = m_plugin_name;\
DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\ DFhackDataExport const char * plugin_version = DFHack::Version::dfhack_version();\
DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\ DFhackDataExport const char * plugin_git_description = DFHack::Version::git_description();\
DFhackDataExport Plugin *plugin_self = NULL;\ DFhackDataExport int plugin_abi_version = DFHack::Version::dfhack_abi_version();\
DFhackDataExport DFHack::Plugin *plugin_self = NULL;\
std::vector<std::string> _plugin_globals;\ std::vector<std::string> _plugin_globals;\
DFhackDataExport std::vector<std::string>* plugin_globals = &_plugin_globals; \ DFhackDataExport std::vector<std::string>* plugin_globals = &_plugin_globals; \
DFhackDataExport bool plugin_dev = is_dev; DFhackDataExport bool plugin_dev = is_dev;

@ -150,10 +150,10 @@ namespace DFHack
class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase { class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase {
public: public:
bool bind(RemoteClient *client, const std::string &name, bool bind(RemoteClient *client, const std::string &name,
const std::string &proto = std::string()); const std::string &plugin = std::string());
bool bind(color_ostream &out, bool bind(color_ostream &out,
RemoteClient *client, const std::string &name, RemoteClient *client, const std::string &name,
const std::string &proto = std::string()); const std::string &plugin = std::string());
bool isValid() { return (id >= 0); } bool isValid() { return (id >= 0); }
@ -167,7 +167,7 @@ namespace DFHack
inline color_ostream &default_ostream(); inline color_ostream &default_ostream();
command_result execute(color_ostream &out, const message_type *input, message_type *output); command_result execute(color_ostream &out, const message_type *input, message_type *output);
std::string name, proto; std::string name, plugin;
RemoteClient *p_client; RemoteClient *p_client;
int16_t id; int16_t id;
}; };
@ -227,12 +227,13 @@ namespace DFHack
friend class RemoteFunctionBase; friend class RemoteFunctionBase;
bool bind(color_ostream &out, RemoteFunctionBase *function, bool bind(color_ostream &out, RemoteFunctionBase *function,
const std::string &name, const std::string &proto); const std::string &name, const std::string &plugin);
public: public:
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; };
@ -268,8 +269,8 @@ namespace DFHack
} }
inline bool RemoteFunctionBase::bind(RemoteClient *client, const std::string &name, inline bool RemoteFunctionBase::bind(RemoteClient *client, const std::string &name,
const std::string &proto) { const std::string &plugin) {
return bind(client->default_output(), client, name, proto); return bind(client->default_output(), client, name, plugin);
} }
class RemoteSuspender { class RemoteSuspender {

@ -46,7 +46,10 @@ namespace DFHack
SF_CALLED_ONCE = 1, SF_CALLED_ONCE = 1,
// Don't automatically suspend the core around the call. // Don't automatically suspend the core around the call.
// The function is supposed to manage locking itself. // The function is supposed to manage locking itself.
SF_DONT_SUSPEND = 2 SF_DONT_SUSPEND = 2,
// The function is considered safe to call from a remote computer.
// All other functions cannot be allowed for security reasons.
SF_ALLOW_REMOTE = 4
}; };
class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase { class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase {
@ -148,6 +151,7 @@ namespace DFHack
class DFHACK_EXPORT RPCService { class DFHACK_EXPORT RPCService {
friend class ServerConnection; friend class ServerConnection;
friend class Plugin; friend class Plugin;
friend class Core;
std::vector<ServerFunctionBase*> functions; std::vector<ServerFunctionBase*> functions;
std::map<std::string, ServerFunctionBase*> lookup; std::map<std::string, ServerFunctionBase*> lookup;
@ -205,6 +209,8 @@ namespace DFHack
assert(!owner); assert(!owner);
functions.push_back(new VoidServerMethod<Svc,In>(this, name, flags, fptr)); functions.push_back(new VoidServerMethod<Svc,In>(this, name, flags, fptr));
} }
void dumpMethods(std::ostream & out) const;
}; };
class ServerConnection { class ServerConnection {

@ -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:

@ -0,0 +1,7 @@
#cmakedefine DFHACK_GIT_DESCRIPTION "@DFHACK_GIT_DESCRIPTION@"
#cmakedefine DFHACK_GIT_COMMIT "@DFHACK_GIT_COMMIT@"
#cmakedefine DFHACK_GIT_XML_EXPECTED_COMMIT "@DFHACK_GIT_XML_EXPECTED_COMMIT@"
#cmakedefine DFHACK_GIT_XML_COMMIT "@DFHACK_GIT_XML_COMMIT@"
#define DFHACK_BUILD_ID "@DFHACK_BUILD_ID@"
#cmakedefine DFHACK_GIT_TAGGED
#cmakedefine DFHACK_GIT_XML_MATCH

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

@ -198,6 +198,15 @@ DFHACK_EXPORT bool markedForRemoval(df::building *bld);
void updateBuildings(color_ostream& out, void* ptr); void updateBuildings(color_ostream& out, void* ptr);
void clearBuildings(color_ostream& out); void clearBuildings(color_ostream& out);
/**
* If the building is a room, returns a description including quality modifiers, e.g. "Royal Bedroom".
* Otherwise, returns an empty string.
*
* The unit argument is passed through to DF and may modify the room's value depending on the unit given.
*/
DFHACK_EXPORT std::string getRoomDescription(df::building *building, df::unit *unit = nullptr);
/** /**
* Iterates over the items stored on a stockpile. * Iterates over the items stored on a stockpile.
* (For stockpiles with containers, yields the containers, not their contents.) * (For stockpiles with containers, yields the containers, not their contents.)

@ -150,6 +150,8 @@ namespace DFHack
DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false); DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false);
DFHACK_EXPORT bool refreshSidebar(); DFHACK_EXPORT bool refreshSidebar();
DFHACK_EXPORT bool inRenameBuilding();
DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z); DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z);
DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z); DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z);

@ -42,8 +42,8 @@ namespace DFHack {
} }
class Callback { class Callback {
T_func *func;
T_hook *hook; T_hook *hook;
T_func *func;
bool enabled; bool enabled;
public: public:
Callback(T_hook *hook, T_func *func) : hook(hook), func(func) Callback(T_hook *hook, T_func *func) : hook(hook), func(func)

@ -182,6 +182,19 @@ DFHACK_EXPORT int getItemBaseValue(int16_t item_type, int16_t item_subtype, int1
DFHACK_EXPORT int getValue(df::item *item); DFHACK_EXPORT int getValue(df::item *item);
DFHACK_EXPORT int32_t createItem(df::item_type type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, df::unit* creator); DFHACK_EXPORT int32_t createItem(df::item_type type, int16_t item_subtype, int16_t mat_type, int32_t mat_index, df::unit* creator);
/// Returns true if the item is free from mandates, or false if mandates prevent trading the item
DFHACK_EXPORT bool checkMandates(df::item *item);
/// Checks whether the item can be traded
DFHACK_EXPORT bool canTrade(df::item *item);
/// Checks whether the item and all items it contains, if any, can be traded
DFHACK_EXPORT bool canTradeWithContents(df::item *item);
/// Checks whether the item is an assigned hauling vehicle
DFHACK_EXPORT bool isRouteVehicle(df::item *item);
/// Checks whether the item is assigned to a squad
DFHACK_EXPORT bool isSquadEquipment(df::item *item);
} }
} }

@ -32,6 +32,7 @@ distribution.
#include "VersionInfo.h" #include "VersionInfo.h"
#include "Core.h" #include "Core.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "df/kitchen_exc_type.h"
/** /**
* \defgroup grp_kitchen Kitchen settings * \defgroup grp_kitchen Kitchen settings
@ -42,14 +43,11 @@ namespace DFHack
{ {
namespace Kitchen namespace Kitchen
{ {
typedef uint8_t t_exclusionType;
const unsigned int seedLimit = 400; // a limit on the limits which can be placed on seeds const unsigned int seedLimit = 400; // a limit on the limits which can be placed on seeds
const t_itemSubtype organicSubtype = -1; // seems to fixed const t_itemSubtype organicSubtype = -1; // seems to fixed
const t_exclusionType cookingExclusion = 1; // seems to be fixed
const df::enums::item_type::item_type limitType = df::enums::item_type::BAR; // used to store limit as an entry in the exclusion list. 0 = BAR const df::enums::item_type::item_type limitType = df::enums::item_type::BAR; // used to store limit as an entry in the exclusion list. 0 = BAR
const t_itemSubtype limitSubtype = 0; // used to store limit as an entry in the exclusion list const t_itemSubtype limitSubtype = 0; // used to store limit as an entry in the exclusion list
const t_exclusionType limitExclusion = 4; // used to store limit as an entry in the exclusion list const df::kitchen_exc_type limitExclusion = df::kitchen_exc_type(4); // used to store limit as an entry in the exclusion list
/** /**
* Kitchen exclusions manipulator. Currently geared towards plants and seeds. * Kitchen exclusions manipulator. Currently geared towards plants and seeds.
@ -79,5 +77,21 @@ DFHACK_EXPORT void setLimit(t_materialIndex materialIndex, unsigned int limit);
DFHACK_EXPORT void clearLimits(); DFHACK_EXPORT void clearLimits();
DFHACK_EXPORT std::size_t size(); DFHACK_EXPORT std::size_t size();
// Finds the index of a kitchen exclusion in ui.kitchen.exc_types. Returns -1 if not found.
DFHACK_EXPORT int findExclusion(df::kitchen_exc_type type,
df::item_type item_type, int16_t item_subtype,
int16_t mat_type, int32_t mat_index);
// Adds an exclusion. Returns false if already excluded.
DFHACK_EXPORT bool addExclusion(df::kitchen_exc_type type,
df::item_type item_type, int16_t item_subtype,
int16_t mat_type, int32_t mat_index);
// Removes an exclusion. Returns false if not excluded.
DFHACK_EXPORT bool removeExclusion(df::kitchen_exc_type type,
df::item_type item_type, int16_t item_subtype,
int16_t mat_type, int32_t mat_index);
} }
} }

@ -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;
} }
@ -53,7 +55,7 @@ class Block;
struct BiomeInfo { struct BiomeInfo {
// Determined by the 4-bit index in the designation bitfield // Determined by the 4-bit index in the designation bitfield
static const unsigned MAX_LAYERS = 16; static constexpr unsigned MAX_LAYERS = 16;
df::coord2d pos; df::coord2d pos;
int default_soil, default_stone, lava_stone; int default_soil, default_stone, lava_stone;
@ -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,67 +234,73 @@ 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;
} }
int32_t priorityAt(df::coord2d p);
bool setPriorityAt(df::coord2d p, int32_t priority);
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);
@ -300,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()
@ -313,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);
@ -339,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
@ -544,11 +554,27 @@ class DFHACK_EXPORT MapCache
Block * b= BlockAtTile(tilecoord); Block * b= BlockAtTile(tilecoord);
return b ? b->DesignationAt(tilecoord) : df::tile_designation(); return b ? b->DesignationAt(tilecoord) : df::tile_designation();
} }
bool setDesignationAt (DFCoord tilecoord, df::tile_designation des) // priority is optional, only set if >= 0
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, priority))
return b->setDesignationAt(tilecoord, des);
return false; return false;
return true;
}
return false;
}
int32_t priorityAt (DFCoord tilecoord)
{
Block *b = BlockAtTile(tilecoord);
return b ? b->priorityAt(tilecoord) : -1;
}
bool setPriorityAt (DFCoord tilecoord, int32_t priority)
{
Block *b = BlockAtTile(tilecoord);
return b ? b->setPriorityAt(tilecoord, priority) : false;
} }
df::tile_occupancy occupancyAt (DFCoord tilecoord) df::tile_occupancy occupancyAt (DFCoord tilecoord)
@ -578,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;

@ -46,6 +46,7 @@ distribution.
namespace df { namespace df {
struct block_square_event; struct block_square_event;
struct block_square_event_designation_priorityst;
struct block_square_event_frozen_liquidst; struct block_square_event_frozen_liquidst;
struct block_square_event_grassst; struct block_square_event_grassst;
struct block_square_event_item_spatterst; struct block_square_event_item_spatterst;
@ -166,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];
} }
@ -321,7 +324,8 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,
std::vector<df::block_square_event_grassst *>* grass = 0, std::vector<df::block_square_event_grassst *>* grass = 0,
std::vector<df::block_square_event_world_constructionst *>* constructions = 0, std::vector<df::block_square_event_world_constructionst *>* constructions = 0,
std::vector<df::block_square_event_spoorst *>* spoors = 0, std::vector<df::block_square_event_spoorst *>* spoors = 0,
std::vector<df::block_square_event_item_spatterst *>* items = 0 std::vector<df::block_square_event_item_spatterst *>* items = 0,
std::vector<df::block_square_event_designation_priorityst *>* priorities = 0
); );
/// remove a block event from the block by address /// remove a block event from the block by address

@ -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 {
@ -333,6 +347,8 @@ namespace DFHack
virtual df::job *getSelectedJob() { return nullptr; } virtual df::job *getSelectedJob() { return nullptr; }
virtual df::building *getSelectedBuilding() { return nullptr; } virtual df::building *getSelectedBuilding() { return nullptr; }
virtual df::plant *getSelectedPlant() { return nullptr; } virtual df::plant *getSelectedPlant() { return nullptr; }
static virtual_identity _identity;
}; };
class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen {
@ -373,5 +389,8 @@ namespace DFHack
virtual df::job *getSelectedJob(); virtual df::job *getSelectedJob();
virtual df::building *getSelectedBuilding(); virtual df::building *getSelectedBuilding();
virtual df::plant *getSelectedPlant(); virtual df::plant *getSelectedPlant();
static virtual_identity _identity;
}; };
} }

@ -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);
@ -146,6 +147,9 @@ DFHACK_EXPORT bool isTrained(df::unit* unit);
DFHACK_EXPORT bool isGay(df::unit* unit); DFHACK_EXPORT bool isGay(df::unit* unit);
DFHACK_EXPORT bool isNaked(df::unit* unit); DFHACK_EXPORT bool isNaked(df::unit* unit);
DFHACK_EXPORT bool isUndead(df::unit* unit); DFHACK_EXPORT bool isUndead(df::unit* unit);
DFHACK_EXPORT bool isGhost(df::unit *unit);
DFHACK_EXPORT bool isActive(df::unit *unit);
DFHACK_EXPORT bool isKilled(df::unit *unit);
DFHACK_EXPORT bool isGelded(df::unit* unit); DFHACK_EXPORT bool isGelded(df::unit* unit);
DFHACK_EXPORT bool isDomesticated(df::unit* unit); DFHACK_EXPORT bool isDomesticated(df::unit* unit);
@ -180,5 +184,10 @@ DFHACK_EXPORT std::string getSquadName(df::unit *unit);
DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit); DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit);
DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit); DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit);
// stress categories - 0 is highest stress
DFHACK_EXPORT extern const std::vector<int32_t> stress_cutoffs;
DFHACK_EXPORT int getStressCategory(df::unit *unit);
DFHACK_EXPORT int getStressCategoryRaw(int32_t stress_level);
} }
} }

@ -1,44 +0,0 @@
#pragma once
/**
* \defgroup grp_vermin Wild vermin (ants, bees, etc)
*/
#include "Export.h"
#include "DataDefs.h"
#include "df/vermin.h"
namespace DFHack
{
namespace Vermin
{
/**
* Structure for holding a read DF vermin spawn point object
* \ingroup grp_vermin
*/
struct t_vermin
{
df::vermin * origin;
int16_t race;
int16_t caste;
uint16_t x;
uint16_t y;
uint16_t z;
uint32_t countdown;
bool visible:1;
bool is_colony:1; /// Is vermin object a colony?
};
static const uint16_t TYPE_WILD_COLONY = 0xFFFF;
/**
* Get number of vermin objects
*/
DFHACK_EXPORT uint32_t getNumVermin();
/**
* Read from vermin object
*/
DFHACK_EXPORT bool Read (const uint32_t index, t_vermin & point);
/**
* Write into vermin object
*/
DFHACK_EXPORT bool Write (const uint32_t index, t_vermin & point);
}// end DFHack::Vermin
}

@ -17,6 +17,7 @@ local function load_patch(name)
local old_bytes = {} local old_bytes = {}
local new_bytes = {} local new_bytes = {}
local has_bytes = false
for line in file:lines() do for line in file:lines() do
if string.match(line, '^%x+:') then if string.match(line, '^%x+:') then
@ -34,10 +35,14 @@ local function load_patch(name)
old_bytes[offset] = oldv old_bytes[offset] = oldv
new_bytes[offset] = newv new_bytes[offset] = newv
has_bytes = true
end end
end end
file:close() file:close()
if not has_bytes then
return nil, 'no patch bytes found'
end
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes } return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
end end

@ -61,6 +61,10 @@ function dfhack.pcall(f, ...)
end end
function qerror(msg, level) function qerror(msg, level)
local name = dfhack.current_script_name()
if name and not tostring(msg):match(name) then
msg = name .. ': ' .. tostring(msg)
end
dfhack.error(msg, (level or 1) + 1, false) dfhack.error(msg, (level or 1) + 1, false)
end end
@ -176,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
@ -330,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
@ -354,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
@ -562,6 +680,61 @@ function dfhack.run_script_with_env(envVars, name, flags, ...)
return script_code(...), env return script_code(...), env
end end
function dfhack.current_script_name()
local frame = 1
while true do
local info = debug.getinfo(frame, 'f')
if not info then break end
if info.func == dfhack.run_script_with_env then
local i = 1
while true do
local name, value = debug.getlocal(frame, i)
if not name then break end
if name == 'name' then
return value
end
i = i + 1
end
break
end
frame = frame + 1
end
end
function dfhack.script_help(script_name, extension)
script_name = script_name or dfhack.current_script_name()
extension = extension or 'lua'
local full_name = script_name .. '.' .. extension
local path = dfhack.internal.findScript(script_name .. '.' .. extension)
or error("Could not find script: " .. full_name)
local begin_seq, end_seq
if extension == 'rb' then
begin_seq = '=begin'
end_seq = '=end'
else
begin_seq = '[====['
end_seq = ']====]'
end
local f = io.open(path) or error("Could not open " .. path)
local in_help = false
local help = ''
for line in f:lines() do
if line:endswith(begin_seq) then
in_help = true
elseif in_help then
if line:endswith(end_seq) then
break
end
if line ~= script_name and line ~= ('='):rep(#script_name) then
help = help .. line .. '\n'
end
end
end
f:close()
help = help:gsub('^\n+', ''):gsub('\n+$', '')
return help
end
local function _run_command(...) local function _run_command(...)
args = {...} args = {...}
if type(args[1]) == 'table' then if type(args[1]) == 'table' then

@ -476,7 +476,7 @@ local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2)
end end
local function scanRawsReaction(buildingId,workshopId,customId) local function scanRawsReaction(buildingId,workshopId,customId)
local ret={} local ret={}
for idx,reaction in ipairs(df.global.world.raws.reactions) do for idx,reaction in ipairs(df.global.world.raws.reactions.reactions) do
for k,v in pairs(reaction.building.type) do for k,v in pairs(reaction.building.type) do
if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then
table.insert(ret,reaction) table.insert(ret,reaction)

@ -105,10 +105,11 @@ local function parse_inset(inset)
l = inset or 0 l = inset or 0
t,r,b = l,l,l t,r,b = l,l,l
end end
return l,r,t,b return l,t,r,b
end end
function inset_frame(rect, inset, gap) function inset_frame(rect, inset, gap)
if not rect then return mkdims_wh(0, 0, 0, 0) end
gap = gap or 0 gap = gap or 0
local l,t,r,b = parse_inset(inset) local l,t,r,b = parse_inset(inset)
return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)

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